@chakra-ui/react#InputGroup TypeScript Examples

The following examples show how to use @chakra-ui/react#InputGroup. 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: SearchInput.tsx    From ksana.in with Apache License 2.0 6 votes vote down vote up
export function SearchInput({ searchText, onChangeSearch }: ISearchInputProps) {
  return (
    <Box width={{ base: '100%' }}>
      <Stack spacing={4}>
        <InputGroup>
          <Input
            size="lg"
            borderWidth="2px"
            borderColor="orange.400"
            name="searchText"
            placeholder="Cari tautan kamu"
            variant="filled"
            value={searchText}
            onChange={onChangeSearch}
          />
          <InputRightElement
            fontSize="2em"
            color="orange.400"
            mr="2"
            mt="1"
            children={<FiSearch />}
          />
        </InputGroup>
      </Stack>
    </Box>
  )
}
Example #2
Source File: ChakraDateInput.tsx    From ke with MIT License 6 votes vote down vote up
ChakraDateInput = forwardRef<HTMLInputElement, ChakraDateInputProps>(
  ({ className, inputClassName, ...props }, ref) => (
    <InputGroup className={className}>
      <InputLeftElement
        zIndex="unset"
        fontSize="20px"
        width="44px"
        justifyContent="flex-start"
        pl="16px"
        pointerEvents="none"
      >
        <Icon as={Calendar} />
      </InputLeftElement>
      {/* Это обёртка */}
      {/* eslint-disable-next-line react/jsx-props-no-spreading */}
      <Input paddingStart="44px" className={inputClassName} {...props} ref={ref} />
    </InputGroup>
  )
)
Example #3
Source File: ImagesPanel.tsx    From react-design-editor with MIT License 6 votes vote down vote up
function ImagesPanel() {
  return (
    <>
      <div style={{ padding: '1rem 2rem' }}>
        <InputGroup>
          <InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
          <Input style={{ background: '#fff' }} type="tel" placeholder="Search images" />
        </InputGroup>
      </div>
    </>
  )
}
Example #4
Source File: MusicPanel.tsx    From react-design-editor with MIT License 6 votes vote down vote up
function MusicPanel() {
  return (
    <>
      <div style={{ padding: '1rem 2rem' }}>
        <InputGroup>
          <InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
          <Input style={{ background: '#fff' }} type="tel" placeholder="Search music" />
        </InputGroup>
      </div>
    </>
  )
}
Example #5
Source File: TemplatesPanel.tsx    From react-design-editor with MIT License 6 votes vote down vote up
function TemplatesPanel() {
  return (
    <>
      <div style={{ padding: '1rem 2rem' }}>
        <InputGroup>
          <InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
          <Input style={{ background: '#fff' }} type="tel" placeholder="Search templates" />
        </InputGroup>
      </div>
    </>
  )
}
Example #6
Source File: VideosPanel.tsx    From react-design-editor with MIT License 6 votes vote down vote up
function VideosPanel() {
  return (
    <>
      <div style={{ padding: '1rem 2rem' }}>
        <InputGroup>
          <InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
          <Input style={{ background: '#fff' }} type="tel" placeholder="Search videos" />
        </InputGroup>
      </div>
    </>
  )
}
Example #7
Source File: InputGroupPreview.tsx    From openchakra with MIT License 6 votes vote down vote up
InputGroupPreview: React.FC<{ component: IComponent }> = ({
  component,
}) => {
  const { drop, isOver } = useDropComponent(component.id)
  const { props, ref } = useInteractive(component, true)

  const boxProps: any = {}

  if (isOver) {
    props.bg = 'teal.50'
  }

  return (
    <Box {...boxProps} ref={drop(ref)}>
      <InputGroup {...props}>
        {component.children.map((key: string) => (
          <ComponentPreview key={key} componentName={key} />
        ))}
      </InputGroup>
    </Box>
  )
}
Example #8
Source File: ObjectsPanel.tsx    From react-design-editor with MIT License 5 votes vote down vote up
function ObjectsPanel() {
  const [search, setSearch] = useState('')
  const [objects, setObjects] = useState<any[]>([])
  const [value] = useDebounce(search, 1000)
  const { canvas } = useCanvasContext()

  useEffect(() => {
    getImages('love')
      .then((data: any) => setObjects(data))
      .catch(console.log)
  }, [])

  useEffect(() => {
    if (value) {
      getImages(value)
        .then((data: any) => setObjects(data))
        .catch(console.log)
    }
  }, [value])
  const renderItems = () => {
    return objects.map(obj => {
      return (
        <div className="object-item-container" onClick={() => downloadImage(obj.uuid)} key={obj.uuid}>
          <img className="object-item" src={obj.urls.thumb} />
        </div>
      )
    })
  }
  const downloadImage = uuid => {
    getImage(uuid)
      .then(url => {
        fabric.loadSVGFromURL(url, (objects, options) => {
          const object = fabric.util.groupSVGElements(objects, options)
          //@ts-ignore
          const workarea = canvas.getObjects().find(obj => obj.id === 'workarea')
          canvas.add(object)
          object.scaleToHeight(300)
          object.center()
          object.clipPath = workarea
          canvas.renderAll()
        })
      })
      .catch(console.log)
  }

  return (
    <>
      <div style={{ padding: '1rem 2rem' }}>
        <InputGroup>
          <InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
          <Input
            onChange={e => setSearch(e.target.value)}
            style={{ background: '#fff' }}
            type="tel"
            placeholder="Search objects"
          />
        </InputGroup>
      </div>
      <div style={{ padding: '0 2rem' }} className="objects-list">
        {renderItems()}
      </div>
    </>
  )
}
Example #9
Source File: TextPanel.tsx    From react-design-editor with MIT License 5 votes vote down vote up
function TextPanel() {
  const { addObject } = useCoreHandler()
  const addHeading = () => {
    const options = {
      type: 'text',
      text: 'Add a heading',
      fontSize: 32,
      width: 320,
      fontWeight: 700,
      fontFamily: 'Lexend',
      textAlign: 'center',
    }
    addObject(options)
  }

  const addSubheading = () => {
    const options = {
      type: 'text',
      text: 'Add a subheading',
      fontSize: 24,
      width: 320,
      fontWeight: 500,
      fontFamily: 'Lexend',
      textAlign: 'center',
    }
    addObject(options)
  }

  const addTextBody = () => {
    const options = {
      type: 'text',
      text: 'Add a little bit of body text',
      fontSize: 18,
      width: 320,
      fontWeight: 300,
      fontFamily: 'Lexend',
      textAlign: 'center',
    }
    addObject(options)
  }
  return (
    <>
      <div className="panel-text" style={{ padding: '1rem 1.5rem' }}>
        <InputGroup>
          <InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.600" />} />
          <Input style={{ background: '#fff' }} type="tel" placeholder="Search text" />
        </InputGroup>
        <div className="label">Click text to add to page</div>
        <div className="add-text-items">
          <div onClick={addHeading} className="add-text-item add-heading">
            Add a heading
          </div>
          <div onClick={addSubheading} className="add-text-item add-subheading">
            Add a subheading
          </div>
          <div onClick={addTextBody} className="add-text-item add-body-text">
            Add a litle bit of body text
          </div>
        </div>
      </div>
    </>
  )
}
Example #10
Source File: index.tsx    From engine with MIT License 5 votes vote down vote up
Details = () => (
  <AccordionItem>
    <h2>
      <AccordionButton _expanded={{ bg: "gray.300", color: "white" }}>
        <Box flex="1" textAlign="left">
          Details
        </Box>
        <AccordionIcon />
      </AccordionButton>
    </h2>
    <AccordionPanel pb={4}>
      <InputGroup size="sm">
        <InputLeftAddon children="Name" w="24" />
        <Input defaultValue="DoThingsFoo" />
      </InputGroup>
      <InputGroup size="sm">
        <InputLeftAddon children="File path" w="24" />
        <Input defaultValue="src/producers/domain/computation.ts" />
      </InputGroup>
      <InputGroup size="sm">
        <InputLeftAddon children="Author" w="24" />
        <Input defaultValue="John Doe" isReadOnly />
      </InputGroup>
      <InputGroup size="sm">
        <InputLeftAddon children="Created on" w="24" />
        <Input defaultValue="19/02/2022" isReadOnly />
      </InputGroup>
      <InputGroup size="sm">
        <InputLeftAddon children="Version" w="24" />
        <Select placeholder="Select a version">
          <option value="option1" selected>
            V2 22/02/2022 14:23 - John a2a2b227a7 (latest)
          </option>
          <option value="option2">V1 20/02/2022 13:23 Jane 9c32e516af</option>
        </Select>
      </InputGroup>
      <InputGroup size="sm">
        <InputLeftAddon children="Description" w="24" />
        <Textarea defaultValue="Does what it needs to do in terms of computation"></Textarea>
      </InputGroup>
    </AccordionPanel>
  </AccordionItem>
)
Example #11
Source File: Profile.tsx    From dope-monorepo with GNU General Public License v3.0 5 votes vote down vote up
Profile = () => {
  const [section, setSection] = useQueryParam('section', SECTIONS[0]);
  const [searchValue, setSearchValue] = useQueryParam('q', '');
  const debouncedSearchValue = useDebounce<string>(searchValue, 250);

  const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
    const value = event.target.value;
    setSearchValue(value);
  };

  return (
    <>
      <Stack
        margin="0"
        spacing="8px"
        width="100%"
        padding="16px"
        background="white"
        borderBottom="2px solid black"
        direction={['column', 'column', 'row']}
      >
        <InputGroup>
          <Input
            className="search"
            placeholder="Search…"
            size="sm"
            border="2px solid black"
            borderRadius="4px"
            onChange={handleSearchChange}
            value={searchValue}
            _focus={{ boxShadow: '0' }}
          />
          {searchValue !== '' && (
            <InputRightElement height="100%">
              <Image
                width="16px"
                src="/images/icon/circle-clear-input.svg"
                alt="Search"
                onClick={() => setSearchValue('')}
                cursor="pointer"
              />
            </InputRightElement>
          )}
        </InputGroup>
      </Stack>
      <Accordion
        allowToggle
        defaultIndex={SECTIONS.findIndex(val => val === section)}
        onChange={idx => {
          if (idx == -1) return;
          const sectionIdx = Array.isArray(idx) ? idx[0] : idx;
          setSection(SECTIONS[sectionIdx]);
        }}
      >
        <Section>
          <Hustlers searchValue={debouncedSearchValue} />
        </Section>
        <Section>
          <Gear searchValue={debouncedSearchValue} />
        </Section>
        <Section>
          <Dopes searchValue={debouncedSearchValue} />
        </Section>
      </Accordion>
    </>
  );
}
Example #12
Source File: SearchInput.tsx    From lucide with ISC License 5 votes vote down vote up
SearchInput = (
  ({ onChange, count }: SearchInputProps) => {
    const { colorMode } = useColorMode();

    const [urlValue, setUrlValue] = useRouterParam('search');

    const [inputValue, setInputValue] = useState('');
    const debouncedValue = useDebounce(inputValue.trim(), 300);

    useUpdateEffect(() => {
      onChange(debouncedValue);
      setUrlValue(debouncedValue);
    }, [debouncedValue]);

    useEffect(() => {
      if (urlValue && !inputValue) {
        setInputValue(urlValue);
        onChange(urlValue);
      }
    }, [urlValue]);
  
    const ref = useRef(null);
    
    // Keyboard `/` shortcut
    useEffect(() => {
      const handleKeyDown = (event: KeyboardEvent) => {
        if (event.key === '/' && ref.current !== document.activeElement) {
          event.preventDefault();
          ref.current.focus();
        }
      };
  
      window.addEventListener('keydown', handleKeyDown);
      return () => window.removeEventListener('keydown', handleKeyDown);
    }, []);

    return (
      <InputGroup position="sticky" top={4} zIndex={1}>
        <InputLeftElement
          children={
            <Icon>
              <SearchIcon />
            </Icon>
          }
        />
        <Input
          ref={ref}
          placeholder={`Search ${count} icons (Press "/" to focus)`}
          onChange={(event) => setInputValue(event.target.value)}
          value={inputValue}
          bg={colorMode == 'light' ? theme.colors.white : theme.colors.gray[700]}
        />
      </InputGroup>
    );
  }
)
Example #13
Source File: ColorPicker.tsx    From lucide with ISC License 5 votes vote down vote up
function ColorPicker({ hsv, hsl, onChange, value: color }: ColorPickerProps) {
  const [value, setValue] = useState(color);
  const input = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (color !== value && input.current !== document.activeElement) {
      setValue(color === 'currentColor' ? color : String(color).toUpperCase());
    }
  }, [color]);

  const handleChange = (e) => {
    let value = e.target.value;
    setValue(value);
    onChange(value, e);
  };

  return (
    <div>
      <FormLabel htmlFor="color" fontWeight={'bold'}>
        Color
      </FormLabel>
      <InputGroup>
        <InputLeftElement
          children={
            <Icon>
              <rect x={0} width={24} y={0} height={24} fill={value} rx={2} />
            </Icon>
          }
        />
        <Input value={value} name="color" onChange={handleChange} ref={input} />
      </InputGroup>
      <div
        style={{
          width: '100%',
          paddingBottom: '75%',
          position: 'relative',
          overflow: 'hidden',
          marginTop: '0.5rem',
          borderRadius: '0.375rem',
          border: '1px solid',
          borderColor: 'inherit',
        }}
      >
        <Saturation hsl={hsl} hsv={hsv} onChange={onChange} />
      </div>
      <div
        style={{
          minHeight: '2em',
          position: 'relative',
          margin: '0.5rem 0 0 0',
          borderRadius: '0.375rem',
          border: '1px solid',
          borderColor: 'inherit',
          overflow: 'hidden',
        }}
      >
        <Hue hsl={hsl} onChange={onChange} direction={'horizontal'} />
      </div>
    </div>
  );
}
Example #14
Source File: index.tsx    From dope-monorepo with GNU General Public License v3.0 5 votes vote down vote up
PlayerPanel = (props: {player: Player}) => {
    const player = props.player;

    return (
        <Accordion>
            <AccordionItem>
                <h2>
                    <AccordionButton>
                        <Box flex='1' textAlign='left'>
                        Basic info
                        </Box>
                        <AccordionIcon />
                    </AccordionButton>
                </h2>
                <AccordionPanel pb={4}>
                    <InputGroup size="sm">
                        <InputLeftAddon children='Name' />
                        <Input onChange={(e) => player.name = e.target.value} placeholder={player.name} />
                    </InputGroup>
                    <div>
                        <Position object={player} />
                    </div>
                </AccordionPanel>
            </AccordionItem>
            <AccordionItem>
                <h2>
                    <AccordionButton>
                        <Box flex='1' textAlign='left'>
                        Quests
                        </Box>
                        <AccordionIcon />
                    </AccordionButton>
                </h2>
                <AccordionPanel pb={4}>
                    {player.questManager.quests.map((quest, i) => {
                        <Text>
                            {quest.name}: {quest.description}
                        </Text>
                    })}
                </AccordionPanel>
            </AccordionItem>
        </Accordion>
    )
}
Example #15
Source File: EditUrlInput.tsx    From coindrop with GNU General Public License v3.0 4 votes vote down vote up
EditUrlInput: FunctionComponent<Props> = ({ register, value }) => {
    const { query: { piggybankName }} = useRouter();
    const currentPiggybankId = Array.isArray(piggybankName) ? piggybankName[0] : piggybankName;
    const [isValidating, setIsValidating] = useState(false);
    const [isValid, setIsValid] = useState(false);
    const [error, setError] = useState<'Invalid input' | 'Id taken' | 'Network error'>();
    const debouncedValue = useDebounce(value, 1500);
    const { setIsPiggybankIdAvailable } = useContext(AdditionalValidation);
    const isUrlUnchanged = value === currentPiggybankId;
    const isInvalid = !debouncedValue.match(piggybankPathRegex);
    const validateIsAvailable = async () => {
        if (isUrlUnchanged) {
            setIsValid(true);
            setIsPiggybankIdAvailable(true);
            setError(null);
            setIsValidating(false);
            return;
        }
        if (isInvalid) {
            setIsValid(false);
            setIsPiggybankIdAvailable(false);
            setError('Invalid input');
            setIsValidating(false);
            return;
        }
        try {
            const isAvailable = await isCoindropUrlAvailable(debouncedValue);
            setIsValid(isAvailable && !isInvalid);
            setIsPiggybankIdAvailable(isAvailable && !isInvalid);
            if (!isAvailable) {
                setError('Id taken');
            }
        } catch (err) {
            setIsValid(false);
            setIsPiggybankIdAvailable(false);
            setError('Network error');
        } finally {
            setIsValidating(false);
        }
    };
    useEffect(() => {
        validateIsAvailable();
    }, [debouncedValue]);
    useEffect(() => {
        setIsValidating(true);
        setError(null);
    }, [value]);
    useEffect(() => {
        setIsValidating(false);
    }, []);
    return (
        <>
        <InputGroup>
            <InputLeftAddon>
                coindrop.to/
            </InputLeftAddon>
            <Input
                id="input-piggybankId"
                maxLength={32}
                roundedLeft="0"
                isInvalid={!isValid && !isValidating && value === debouncedValue && !isUrlUnchanged}
                ref={register}
                name="piggybankId"
            />
            <InputRightElement>
                <StatusIcon
                    value={value}
                    debouncedValue={debouncedValue}
                    currentPiggybankId={currentPiggybankId}
                    isValid={isValid}
                    isValidating={isValidating}
                />
            </InputRightElement>
        </InputGroup>
        {error === 'Invalid input' && (
            <CoindropRequirements />
        )}
        {error === 'Id taken' && (
            <CreateCoindropError
                error="URL is taken, try another!"
            />
        )}
        {error === 'Network error' && (
            <CreateCoindropError
                error="Error checking availability, please try again"
            />
        )}
        </>
    );
}
Example #16
Source File: CustomPropsPanel.tsx    From openchakra with MIT License 4 votes vote down vote up
CustomPropsPanel = () => {
  const dispatch = useDispatch()
  const inputRef = useRef<HTMLInputElement>(null)

  const activePropsRef = useInspectorState()
  const { props, id } = useSelector(getSelectedComponent)
  const { setValue } = useForm()

  const [quickProps, setQuickProps] = useState('')
  const [hasError, setError] = useState(false)

  const onDelete = (propsName: string) => {
    dispatch.components.deleteProps({
      id,
      name: propsName,
    })
  }

  const activeProps = activePropsRef || []
  const customProps = Object.keys(props).filter(
    propsName => !activeProps.includes(propsName),
  )

  return (
    <>
      <form
        onSubmit={(event: FormEvent) => {
          event.preventDefault()

          const [name, value] = quickProps.split(SEPARATOR)

          if (name && value) {
            setValue(name, value)
            setQuickProps('')
            setError(false)
          } else {
            setError(true)
          }
        }}
      >
        <InputGroup mb={3} size="sm">
          <InputRightElement
            children={<Box as={IoIosFlash} color="gray.300" />}
          />
          <Input
            ref={inputRef}
            isInvalid={hasError}
            value={quickProps}
            placeholder={`props${SEPARATOR}value`}
            onChange={(event: ChangeEvent<HTMLInputElement>) =>
              setQuickProps(event.target.value)
            }
          />
        </InputGroup>
      </form>

      {customProps.map((propsName, i) => (
        <Flex
          key={propsName}
          alignItems="center"
          px={2}
          bg={i % 2 === 0 ? 'white' : 'gray.50'}
          fontSize="xs"
          justifyContent="space-between"
        >
          <SimpleGrid width="100%" columns={2} spacing={1}>
            <Box fontWeight="bold">{propsName}</Box>
            <Box>{props[propsName]}</Box>
          </SimpleGrid>

          <ButtonGroup display="flex" size="xs" isAttached>
            <IconButton
              onClick={() => {
                setQuickProps(`${propsName}=`)
                if (inputRef.current) {
                  inputRef.current.focus()
                }
              }}
              variant="ghost"
              size="xs"
              aria-label="edit"
              icon={<EditIcon path="" />}
            />
            <IconButton
              onClick={() => onDelete(propsName)}
              variant="ghost"
              size="xs"
              aria-label="delete"
              icon={<SmallCloseIcon path="" />}
            />
          </ButtonGroup>
        </Flex>
      ))}
    </>
  )
}
Example #17
Source File: PaddingPanel.tsx    From openchakra with MIT License 4 votes vote down vote up
PaddingPanel = ({ type }: PaddingPanelPropsType) => {
  const { setValueFromEvent } = useForm()

  const all = usePropsSelector(ATTRIBUTES[type].all)
  const left = usePropsSelector(ATTRIBUTES[type].left)
  const right = usePropsSelector(ATTRIBUTES[type].right)
  const bottom = usePropsSelector(ATTRIBUTES[type].bottom)
  const top = usePropsSelector(ATTRIBUTES[type].top)

  return (
    <Box mb={4}>
      <FormControl>
        <FormLabel fontSize="xs" htmlFor="width" textTransform="capitalize">
          {type}
        </FormLabel>

        <InputGroup size="sm">
          <Input
            mb={1}
            placeholder="All"
            size="sm"
            type="text"
            name={ATTRIBUTES[type].all}
            value={all || ''}
            onChange={setValueFromEvent}
          />
        </InputGroup>

        <SimpleGrid columns={2} spacing={1}>
          <InputGroup size="sm">
            <InputLeftElement
              children={
                <ArrowBackIcon path="" fontSize="md" color="gray.300" />
              }
            />
            <Input
              placeholder="left"
              size="sm"
              type="text"
              name={ATTRIBUTES[type].left}
              value={left || ''}
              onChange={setValueFromEvent}
              autoComplete="off"
            />
          </InputGroup>

          <InputGroup size="sm">
            <InputLeftElement
              children={
                <ArrowForwardIcon path="" fontSize="md" color="gray.300" />
              }
            />
            <Input
              placeholder="right"
              size="sm"
              type="text"
              value={right || ''}
              name={ATTRIBUTES[type].right}
              onChange={setValueFromEvent}
              autoComplete="off"
            />
          </InputGroup>

          <InputGroup size="sm">
            <InputLeftElement
              children={<ArrowUpIcon path="" fontSize="md" color="gray.300" />}
            />
            <Input
              placeholder="top"
              size="sm"
              type="text"
              value={top || ''}
              name={ATTRIBUTES[type].top}
              onChange={setValueFromEvent}
              autoComplete="off"
            />
          </InputGroup>

          <InputGroup size="sm">
            <InputLeftElement
              children={
                <ChevronDownIcon path="" fontSize="md" color="gray.300" />
              }
            />
            <Input
              placeholder="bottom"
              size="sm"
              type="text"
              value={bottom || ''}
              name={ATTRIBUTES[type].bottom}
              onChange={setValueFromEvent}
              autoComplete="off"
            />
          </InputGroup>
        </SimpleGrid>
      </FormControl>
    </Box>
  )
}
Example #18
Source File: Sidebar.tsx    From openchakra with MIT License 4 votes vote down vote up
Menu = () => {
  const [searchTerm, setSearchTerm] = useState('')

  return (
    <DarkMode>
      <Box
        maxH="calc(100vh - 3rem)"
        overflowY="auto"
        overflowX="visible"
        boxShadow="xl"
        flex="0 0 14rem"
        p={5}
        m={0}
        as="menu"
        backgroundColor="#2e3748"
        width="15rem"
      >
        <InputGroup size="sm" mb={4}>
          <Input
            value={searchTerm}
            color="gray.300"
            placeholder="Search component…"
            onChange={(event: ChangeEvent<HTMLInputElement>) =>
              setSearchTerm(event.target.value)
            }
            borderColor="rgba(255, 255, 255, 0.04)"
            bg="rgba(255, 255, 255, 0.06)"
            _hover={{
              borderColor: 'rgba(255, 255, 255, 0.08)',
            }}
            zIndex={0}
          />
          <InputRightElement zIndex={1}>
            {searchTerm ? (
              <IconButton
                color="gray.300"
                aria-label="clear"
                icon={<CloseIcon path="" />}
                size="xs"
                onClick={() => setSearchTerm('')}
              />
            ) : (
              <SearchIcon path="" color="gray.300" />
            )}
          </InputRightElement>
        </InputGroup>

        {(Object.keys(menuItems) as ComponentType[])
          .filter(c => c.toLowerCase().includes(searchTerm.toLowerCase()))
          .map(name => {
            const { children, soon } = menuItems[name] as MenuItem

            if (children) {
              const elements = Object.keys(children).map(childName => (
                <DragItem
                  isChild
                  key={childName}
                  label={childName}
                  type={childName as any}
                  id={childName as any}
                  rootParentType={menuItems[name]?.rootParentType || name}
                >
                  {childName}
                </DragItem>
              ))

              return [
                <DragItem
                  isMeta
                  soon={soon}
                  key={`${name}Meta`}
                  label={name}
                  type={`${name}Meta` as any}
                  id={`${name}Meta` as any}
                  rootParentType={menuItems[name]?.rootParentType || name}
                >
                  {name}
                </DragItem>,
                ...elements,
              ]
            }

            return (
              <DragItem
                soon={soon}
                key={name}
                label={name}
                type={name as any}
                id={name as any}
                rootParentType={menuItems[name]?.rootParentType || name}
              >
                {name}
              </DragItem>
            )
          })}
      </Box>
    </DarkMode>
  )
}
Example #19
Source File: CreatePiggybankInput.tsx    From coindrop with GNU General Public License v3.0 4 votes vote down vote up
CreatePiggybankInput: FunctionComponent<Props> = ({
    buttonText,
    instanceId,
    onCancel,
    createButtonColorScheme,
    placeholder,
}) => {
    const { user } = useUser();
    const router = useRouter();
    const inputRef = createRef<HTMLInputElement>();
    useEffect(() => {
        if (router.pathname === '/dashboard') {
            inputRef.current.focus();
        }
    }, []);
    const [candidatePiggybankPath, setCandidatePiggybankPath] = useState('');
    const [isCandidatePiggybankPathInvalid, setIsCandidatePiggybankPathInvalid] = useState(false);
    const [isCreateTriggered, setIsCreateTriggered] = useState(false);
    const { submitStatus, error, setError } = useCreatePiggybank(candidatePiggybankPath, setCandidatePiggybankPath, user, isCreateTriggered, setIsCreateTriggered);
    async function handleCreateUrl() {
        const isInvalid = !candidatePiggybankPath.match(piggybankPathRegex);
        if (isInvalid) {
            setIsCandidatePiggybankPathInvalid(true);
        } else if (user) {
            setIsCreateTriggered(true);
        } else if (router.pathname === '/') {
            cookies.set('pendingLoginCreatePiggybankPath', candidatePiggybankPath);
            router.push('/?auth=1', undefined, { shallow: true });
        }
    }
    function onSubmit(event) {
        event.preventDefault();
        handleCreateUrl();
    }
    const inputName = `create-coindrop-input-${instanceId}`;
    return (
        <form id={`create-coindrop-form-${instanceId}`} onSubmit={onSubmit}>
            <Flex
                align="center"
                justify="center"
                wrap="wrap"
            >
                <BoxMargin>
                    <InputGroup>
                        <InputLeftAddon>
                            coindrop.to/
                        </InputLeftAddon>
                        <Input
                            aria-label="Enter custom URL path"
                            name={inputName}
                            id={inputName}
                            maxLength={32}
                            roundedLeft="0"
                            placeholder={placeholder}
                            onChange={(e) => {
                                setError(null);
                                setCandidatePiggybankPath(e.target.value);
                                setIsCandidatePiggybankPathInvalid(false);
                            }}
                            value={candidatePiggybankPath}
                            isInvalid={isCandidatePiggybankPathInvalid || !!error}
                            ref={inputRef}
                        />
                    </InputGroup>
                </BoxMargin>
                <BoxMargin>
                    <Button
                        ml={1}
                        colorScheme={createButtonColorScheme}
                        isDisabled={isCandidatePiggybankPathInvalid || submitStatus === 'submitting' || submitStatus === 'success'}
                        isLoading={submitStatus === 'submitting' || submitStatus === 'success'}
                        loadingText="Creating"
                        onClick={onSubmit}
                        type="submit"
                    >
                        {buttonText}
                    </Button>
                </BoxMargin>
                {onCancel && (
                    <BoxMargin>
                        <Button
                            onClick={onCancel}
                            ml={1}
                        >
                            Cancel
                        </Button>
                    </BoxMargin>
                )}
            </Flex>
            {error && (
                <CreateCoindropError
                    error={error}
                />
            )}
            {isCandidatePiggybankPathInvalid && (
                <CoindropRequirements />
            )}
        </form>
    );
}
Example #20
Source File: index.tsx    From dope-monorepo with GNU General Public License v3.0 4 votes vote down vote up
HustlersPanel = (props: { hustlers: Hustler[] }) => {
    const hustlers = props.hustlers;

    return (
        <Accordion allowToggle>
            {hustlers.map((hustler, i) => 
                <AccordionItem key={i}>
                    <h2>
                        <AccordionButton>
                            <Box flex='1' textAlign='left'>
                                {hustler.name}: {hustler.hustlerId ?? 'No Hustler'}
                                <br/>
                                Citizen: {hustler instanceof Citizen ? '✅' : '❌'}
                            </Box>
                            <AccordionIcon />
                        </AccordionButton>
                    </h2>
                    <AccordionPanel pb={4}>
                        <InputGroup size="sm">
                            <InputLeftAddon children='Name' />
                            <Input onChange={(e) => hustler.name = e.target.value} placeholder={hustler.name} />
                        </InputGroup>
                        <div>
                        <Position object={hustler} />
                        </div>
                        <br />
                        <Accordion allowToggle>
                            {hustler instanceof Citizen ? <div>
                                <AccordionItem>
                                <h2>
                                    <AccordionButton>
                                        <Box flex='1' textAlign='left'>
                                            Path
                                        </Box>
                                        <AccordionIcon />
                                    </AccordionButton>
                                </h2>
                                <AccordionPanel pb={4}>
                                        Repeat path:
                                        <Checkbox defaultChecked={hustler.repeatPath} onChange={(e) => hustler.repeatPath = e.target.checked} />
                                        <br />
                                        Follow path:
                                        <Checkbox defaultChecked={hustler.shouldFollowPath} onChange={(e) => hustler.shouldFollowPath = e.target.checked} />
                                        <br />
                                        <br />
                                        {
                                            hustler.path.map((p, i) => 
                                                <div key={i}>
                                                    PathPoint #{i}
                                                    <Position object={p.position} />
                                                    <br />
                                                </div>
                                            )
                                        }
                                </AccordionPanel>
                            </AccordionItem>
                            <AccordionItem>
                                <h2>
                                    <AccordionButton>
                                        <Box flex='1' textAlign='left'>
                                            Conversations
                                        </Box>
                                        <AccordionIcon />
                                    </AccordionButton>
                                </h2>
                                <AccordionPanel pb={4}>
                                        {
                                            hustler.conversations.map((c, i) => 
                                                <div key={i}>
                                                    <Accordion>
                                                        {
                                                            c.texts.map((t, i) =>
                                                                <AccordionItem key={i}>
                                                                    <h2>
                                                                        <AccordionButton>
                                                                            <Box flex='1' textAlign='left'>
                                                                                {t.text}
                                                                            </Box>
                                                                            <AccordionIcon />
                                                                        </AccordionButton>
                                                                    </h2>
                                                                    <AccordionPanel pb={4}>
                                                                        <InputGroup size="sm">
                                                                            <InputLeftAddon children='Text' />
                                                                            <Input onChange={(e) => t.text = e.target.value} placeholder={t.text} />
                                                                        </InputGroup>
                                                                        <Text>
                                                                            Choices
                                                                        </Text>
                                                                        {
                                                                            t.choices ? t.choices.map((c, i) =>
                                                                                <div key={i}>
                                                                                    <InputGroup size="sm">
                                                                                        <InputLeftAddon children='Text' />
                                                                                        <Input onChange={(e) => (t.choices!)[i] = e.target.value} placeholder={c} />
                                                                                    </InputGroup>
                                                                                </div>
                                                                            ) : undefined
                                                                        }
                                                                    </AccordionPanel>
                                                                </AccordionItem>
                                                            )
                                                        }
                                                    </Accordion>
                                                </div>
                                            )
                                        }
                                </AccordionPanel>
                            </AccordionItem>
                            </div> : undefined}
                        </Accordion>
                    </AccordionPanel>
                </AccordionItem>
            )}
        </Accordion>
        
    )
}
Example #21
Source File: index.tsx    From calories-in with MIT License 4 votes vote down vote up
function FoodsList({
  selection,
  searchInputRef,
  onFoodPreview,
  forwardedRef,
  allowsFiltering = true,
  itemUsageType = 'selectOrPreview',
  ...rest
}: Props) {
  const { allFoods, userFoods } = useFoods()
  const listRef = useRef<FixedSizeList>(null)

  const filter = useFoodsFilter()
  const foodsFilterActions = useFoodsFilterActions()
  const filteredFoods = useFilterFoods(allFoods, userFoods, filter)

  useEffect(() => {
    if (filter.categoryId) {
      listRef.current?.scrollToItem(0, 'start')
    }
  }, [filter.categoryId])

  useImperativeHandle(forwardedRef, () => ({
    scrollToFood: (food: Food) => {
      foodsFilterActions.resetCategoryIdAndQuery()

      if (listRef.current) {
        const foods = filter.onlyFoodsAddedByUser ? userFoods : allFoods
        const index = foods.map(({ id }) => id).indexOf(food.id)
        listRef.current.scrollToItem(index, 'center')
      }
    },
  }))

  function onFoodSelect(food: Food) {
    if (selection) {
      selection.toggleItem(food)
      const input = searchInputRef?.current

      if (input && !isMobile) {
        input.focus()
        input.setSelectionRange(0, input.value.length)
      }
    }
  }

  return (
    <Flex flexDirection="column" {...rest}>
      <HStack spacing={3}>
        {allowsFiltering && (
          <Box>
            <FoodsFilterPopoverOrModal />
          </Box>
        )}

        <InputGroup size="md" flex={4}>
          <InputLeftElement
            pointerEvents="none"
            children={
              <SearchStyled pointerEvents="none" size={20} color="gray.400" />
            }
          />
          <Input
            ref={searchInputRef}
            value={filter.query}
            onChange={(event: ChangeEvent<HTMLInputElement>) =>
              foodsFilterActions.updateFilter({ query: event.target.value })
            }
            placeholder="Search"
          />
        </InputGroup>
      </HStack>

      <Divider mt={3} width="100%" />

      {filteredFoods.length > 0 ? (
        <VirtualizedList
          ref={listRef}
          foodsCount={filteredFoods.length}
          isFoodSelected={food =>
            selection ? selection.isIdSelected(food.id) : false
          }
          getFood={index => filteredFoods[index]}
          onFoodSelect={onFoodSelect}
          onFoodPreview={onFoodPreview || (() => {})}
          itemUsageType={itemUsageType}
        />
      ) : (
        <Flex flex={1} alignItems="center" justifyContent="center">
          <Text textColor="gray.500">No foods found</Text>
        </Flex>
      )}
    </Flex>
  )
}
Example #22
Source File: index.tsx    From ksana.in with Apache License 2.0 4 votes vote down vote up
export function UrlForm({ user, onSuccess }: IUrlFormProps) {
  const { showAlert, hideAlert } = useAlertContext()

  const [url, setUrl] = useState<string>('')
  const [slug, setSlug] = useState<string>('')
  const [isCheckPass, setIsCheckPass] = useState<boolean>(false)
  const [isDynamic, setIsDynamic] = useState<boolean>(false)
  const [errorUrl, setErrorUrl] = useState<boolean | string>(false)
  const [errorSlug, setErrorSlug] = useState<boolean | string>(false)
  const [loading, setLoading] = useState<boolean>(false)

  const handleChangeUrl = (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value
    setUrl(value)
    setErrorUrl('')
  }

  const handleChangeSlug = (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value
    setSlug(value)
    setErrorSlug('')
  }

  const handleChangeIsDynamic = (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.checked
    setIsDynamic(value)
  }

  const resetErrorMessage = () => {
    setErrorUrl('')
    setErrorSlug('')
  }

  const checkIsEmpty = () => {
    if (url === '') {
      setErrorUrl('URL dan slug tidak bisa dikosongkan')
      return true
    }

    if (url.indexOf('http://') === -1 && url.indexOf('https://') === -1) {
      setErrorUrl('Pastikan URL dimulai dengan http:// atau https://')
      return true
    }

    if (slug === '') {
      setErrorSlug('URL dan slug tidak bisa dikosongkan')
      return true
    }

    return false
  }

  const checkParamRequired = () => {
    const params = url.match(/{param}/g) || []

    if (isDynamic && !params.length) {
      setErrorUrl('Tautan dinamis membutuhkan teks {param} di dalamnya')
      return false
    }

    if (isDynamic && params.length > 1) {
      setErrorUrl('Teks {param} cukup satu saja')
      return false
    }

    return true
  }

  const handleCheckAvailability = async () => {
    setLoading(true)
    resetErrorMessage()
    const isEmpty = checkIsEmpty()
    const hasParam = checkParamRequired()
    if (!isEmpty && hasParam) {
      const response = await checkSlug({ slug: sanitizeSlug(slug) })
      if (response.error) {
        setIsCheckPass(true)
        resetErrorMessage()
      } else {
        setErrorSlug(`Slug ${slug} telah digunakan, coba slug lain`)
      }
    }
    setLoading(false)
  }

  const handleSaveNew = async () => {
    setLoading(true)
    const isEmpty = checkIsEmpty()
    if (!isEmpty) {
      const { error: errorInsert } = await saveUrl({
        url: url,
        slug: sanitizeSlug(slug),
        is_dynamic: isDynamic,
        userId: user?.id
      })

      if (!errorInsert) {
        showAlert({
          title: 'Sukses menyimpan tautan baru',
          message: 'Tautan telah disimpan dalam basis data kami, silahkan mulai bagikan',
          onClose: () => {
            hideAlert()
            mutate(apiUrlsGet(user?.id))
            setUrl('')
            setSlug('')
            setIsCheckPass(false)
            resetErrorMessage()
            onSuccess()
          }
        })
      } else {
        showAlert({
          title: 'Terjadi galat pada saat berusaha menyimpan data',
          message: `Pesan: ${errorInsert.message}`,
          onClose: () => {
            hideAlert()
            setIsCheckPass(false)
            resetErrorMessage()
          }
        })
      }
    }
    setLoading(false)
  }

  return (
    <Box width={{ base: '100%' }}>
      <Stack spacing={4} direction={{ base: 'column' }}>
        <FormControl id="url" isRequired>
          <Input
            isRequired
            isInvalid={Boolean(errorUrl)}
            size="lg"
            name="url"
            placeholder={'Tautan yang akan dipercantik'}
            variant="filled"
            value={url}
            onChange={handleChangeUrl}
          />
          {errorUrl && <FormHelperText color="red.500">Error: {errorUrl}</FormHelperText>}
          <FormHelperText>
            Membutuhkan tautan dalam bentuk utuh, termasuk awalan https://
          </FormHelperText>
          {isDynamic && (
            <FormHelperText>
              Sisipkan teks <code>{'{param}'}</code> pada tautan
            </FormHelperText>
          )}
        </FormControl>

        <FormControl display="flex" alignItems="center">
          <FormLabel htmlFor="is-dynamic" mb="0" display="flex">
            Tautan dinamis{' '}
            <Tooltip
              label="Kamu bisa membuat tautan dinamis macam: https://mazipan.space/{param}"
              placement="bottom"
            >
              <i>
                <HiQuestionMarkCircle />
              </i>
            </Tooltip>
          </FormLabel>
          <Switch id="is-dynamic" onChange={handleChangeIsDynamic} />
        </FormControl>

        <FormControl id="slug" isRequired>
          <InputGroup size="lg">
            <InputLeftAddon
              color={'orange.400'}
              fontWeight="bold"
              px={2}
              children={HOME?.replace('https://', '').replace('http://', '')}
              fontSize="xs"
            />
            <Input
              isRequired
              isInvalid={Boolean(errorSlug)}
              size="lg"
              name="slug"
              placeholder={'Slug cantik dambaanmu'}
              variant="filled"
              value={slug}
              onChange={handleChangeSlug}
            />
          </InputGroup>
          {errorSlug && <FormHelperText color="red.500">Error: {errorSlug}</FormHelperText>}
          <FormHelperText>
            Hanya diperbolehkan menggunakan huruf, angka, karakter titik dan strip saja
          </FormHelperText>
        </FormControl>

        {isCheckPass ? (
          <Button
            isLoading={loading}
            loadingText="Processing"
            size="lg"
            px={6}
            mt="4"
            color={'white'}
            bg={'green.400'}
            _hover={{
              bg: 'green.500'
            }}
            _focus={{
              bg: 'green.500'
            }}
            onClick={handleSaveNew}
          >
            Simpan sekarang
          </Button>
        ) : (
          <Button
            isLoading={loading}
            loadingText="Processing"
            size="lg"
            px={6}
            my="4"
            color={'white'}
            bg={'orange.400'}
            _hover={{
              bg: 'orange.500'
            }}
            _focus={{
              bg: 'orange.500'
            }}
            onClick={handleCheckAvailability}
          >
            Cek ketersediaan
          </Button>
        )}
      </Stack>
    </Box>
  )
}
Example #23
Source File: index.tsx    From dope-monorepo with GNU General Public License v3.0 4 votes vote down vote up
LightsPanel = (props: { player: Player, lights: Phaser.GameObjects.LightsManager }) => {
    const [, forceUpdate] = useReducer(x => x + 1, 0);
    
    const player = props.player;
    const lights = props.lights;

    return (
        <>
            <div style={{
                paddingLeft: '1rem',
            }}>
                Enabled: <Checkbox onChange={(e) => lights.active = e.target.checked} defaultChecked={lights.active}/>
                <br/>
                Number of lights: {lights.getLightCount()}
                <br/>
                Ambient color:
                <input 
                    type="color" 
                    onChange={(e) => {
                        const value = e.target.value;
                        const color = value.substring(1);
                        const r = parseInt(color.substring(0, 2), 16) / 255;
                        const g = parseInt(color.substring(2, 4), 16) / 255;
                        const b = parseInt(color.substring(4, 6), 16) / 255;
                        lights.ambientColor.set(r, g, b);
                    }}
                    defaultValue={'#' + 
                        (lights.ambientColor.r * 255).toString(16) + 
                        (lights.ambientColor.g * 255).toString(16) + 
                        (lights.ambientColor.b * 255).toString(16)}
                />
                <br/>
                <br />
                <Button onClick={(e) => {
                    lights.addLight(player.x, player.y);
                    forceUpdate();
                }}>
                    Add light
                </Button>
                <br />
                <Button onClick={(e) => {
                    let layer: Phaser.GameObjects.Layer;
                    if (!(layer = (lights as any).pointLightLayer))
                        layer = (lights as any).pointLightLayer = player.scene.add.layer();
                    layer.add(lights.addPointLight(player.x, player.y));
                    forceUpdate();
                }}>
                    Add pointlight
                </Button>
            </div>
            <br/>
            <Accordion defaultIndex={0}>
                {
                    // getLights doesnt actually return an array of the lights... it returns an object with the dist and light
                    lights.getLights(player.scene.cameras.main).map((light, i) => {
                        const distance = (light as any).distance;
                        light = (light as any).light;
                        return <AccordionItem key={i}>
                            <h2>
                                <AccordionButton>
                                    <Box flex='1' textAlign='left'>
                                        Light: {light.x} {light.y}
                                        <br />
                                        PointLight: {light instanceof Phaser.GameObjects.PointLight ? '✅' : '❌'}
                                    </Box>
                                    <AccordionIcon />
                                </AccordionButton>
                            </h2>
                            <AccordionPanel pb={4}>
                                <Position object={light}/>
                                <InputGroup size="sm">
                                    <InputLeftAddon children='Radius' />
                                    <Input onChange={(e) => light.radius = Number.parseFloat(e.target.value) ?? 0} placeholder={light.radius.toString()} />
                                </InputGroup>
                                <InputGroup size="sm">
                                    <InputLeftAddon children='Intensity' />
                                    <Input onChange={(e) => light.intensity = Number.parseFloat(e.target.value) ?? 0} placeholder={light.intensity.toString()} />
                                </InputGroup>
                                <br/>
                                Visible: <Checkbox onChange={(e) => light.visible = e.target.checked} defaultChecked={light.visible}/>
                                <div>
                                    Color:
                                    <input 
                                        type="color" 
                                        onChange={(e) => {
                                            const value = e.target.value;
                                            const color = value.substring(1);
                                            const r = parseInt(color.substring(0, 2), 16) / 255;
                                            const g = parseInt(color.substring(2, 4), 16) / 255;
                                            const b = parseInt(color.substring(4, 6), 16) / 255;
                                            light.color.set(r, g, b);
                                        }}
                                        defaultValue={'#' + 
                                            ((light.color as any).r * 255).toString(16).padStart(2, '0') + 
                                            ((light.color as any).g * 255).toString(16).padStart(2, '0') + 
                                            ((light.color as any).b * 255).toString(16).padStart(2, '0')}
                                    />
                                </div>
                                <br/>
                                <Button variant="cny" onClick={(e) => {
                                    if (light instanceof Phaser.GameObjects.PointLight)
                                        (light as any).layer.remove(light);
                                    else
                                        lights.removeLight(light);
                                    forceUpdate();
                                }}>
                                    Remove light
                                </Button>
                            </AccordionPanel>
                        </AccordionItem>
                    })
                }
            </Accordion>
        </>
    )
}
Example #24
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 #25
Source File: ChatType.tsx    From dope-monorepo with GNU General Public License v3.0 4 votes vote down vote up
export default function ChatType(props: Props) {
  const [ unreadMessages, setUnreadMessages ] = React.useState(0);
  const [ inputText, setInputText ] = React.useState('');
  const [ messages, setMessages ] = React.useState(props.messagesStore);
  const [ canSendMessage, setCanSendMessage ] = React.useState((props.chatMessageBoxes?.length ?? 0) < 3);

  const messagesListRef = React.useRef<HTMLUListElement>(null);

  let state = React.useRef({
    i: -1,
  });

  useEffect(() => {
    props.manager.events.on('chat_message', (message: DataTypes[NetworkEvents.SERVER_PLAYER_CHAT_MESSAGE]) => {
      setMessages(m => [...m, message]);
      const lastMessageEl = messagesListRef.current?.lastElementChild as HTMLLIElement;
      if (lastMessageEl && 
        lastMessageEl?.parentElement?.parentElement && !isVisible(lastMessageEl, lastMessageEl?.parentElement?.parentElement)) 
        setUnreadMessages(u => u + 1);
    });

    // constantly check chatMessageBoxes size and if it's less than 3, set canSendMessage to true
    const interval = setInterval(() => setCanSendMessage((props.chatMessageBoxes?.length ?? 0) < 3));
    return () => clearInterval(interval);
  }, []);

  const handleInputKey = (e: KeyboardEvent) => {
    if (e.key === 'Enter' && canSendMessage) handleSubmit(inputText);
    else if (e.key === 'Escape')
      // send "nothing", chat will get closed & message will not get sent
      handleSubmit('');
    else if (e.key === 'ArrowUp') {
      state.current.i = ++state.current.i % props.precedentMessages.length;
      const precedentMessage = props.precedentMessages[state.current.i];
      if (precedentMessage) setInputText(precedentMessage);
    } else if (e.key === 'ArrowDown') {
      // rolling window, wrap around
      state.current.i = --state.current.i % props.precedentMessages.length;
      if (state.current.i < 0) state.current.i = props.precedentMessages.length - 1;

      const precedentMessage = props.precedentMessages[state.current.i];
      if (precedentMessage) setInputText(precedentMessage);
    }
  };

  const handleSubmit = (content: string) => {
    if (content.length > 150)
      return;

    props.manager.events.emit('chat_submit', content);
  };

  return (
    <ChakraProvider theme={theme}>
      <Container 
      style={{
        position: 'absolute',
        backgroundColor: 'rgba(0,0,0,0.7)',
        borderRadius: '0.5rem',
        boxShadow: '0 0.5rem 1rem rgba(0, 0, 0, 0.7)',
        height: '30%',
        width: '30%',
        left: "1%",
        bottom: "1%",
      }}>
        <Stack style={{
          paddingTop: '1rem',
          height: '95%',
        }}>
          <div style={{
            display: 'flex',
            overflow: 'auto',
            flexDirection: 'column-reverse',
            marginBottom: '-3%',
          }}>
            <List ref={messagesListRef} spacing={-2} style={{
            }}>
              <Text style={{
                color: 'blueviolet',
              }}>
                Welcome to the Dopeverse!
              </Text>
              {messages.map((message, i) => <ListItem key={i}>
                  <HStack style={{
                    opacity: '0.8'
                  }}>
                    <Text style={{
                      color: 'white',
                    }}>
                      {message.author}: {message.message}
                    </Text>
                    <Spacer />
                    <Text style={{
                      color: 'grey',
                      fontSize: '0.6rem',
                    }}>
                      {new Date(message.timestamp).toLocaleString()}
                    </Text>
                  </HStack>
              </ListItem>)}
            </List>
          </div>
          <Spacer />
          <Center>
            <Button 
                  style={{
                    marginRight: '1%',
                    marginTop: '-10%'
                  }}
                  variant="primary"
                  backgroundColor="red.600"
                  hidden={inputText.length <= 150} 
                  onClick={() => setInputText(inputText.substring(0, 150))}
                >
                ❌ Message too long
            </Button>
            <Button 
              style={{
                marginTop: '-10%',
              }}
              variant="primary" 
              hidden={unreadMessages === 0} 
              onClick={(e) => {
                setUnreadMessages(0);
                e.currentTarget.hidden = true;
                if (messagesListRef.current)
                  (messagesListRef.current as HTMLOListElement).lastElementChild?.scrollIntoView({
                    behavior: 'smooth',
                  });
              }}
            >
              ⬇️ New message ({unreadMessages})
            </Button>
          </Center>
          <Center>
            <InputGroup width="90%" size="md">
              <Input
                autoFocus={true}
                focusBorderColor="white"
                onBlur={({ target }) => target.focus()}
                pr="4.5rem"
                placeholder="Message"
                _placeholder={{ color: '#b8b8b8' }}
                textColor="#f5f5f5"
                value={inputText}
                onChange={({ target }) => setInputText(target.value)}
                onKeyDown={handleInputKey}
                style={{
                  backgroundColor: 'rgba(0, 0, 0, 0.3)',
                }}
              />
              <InputRightElement width="4.5rem" style={{ paddingRight: '2%' }}>
                <Button h="1.75rem" size="sm" disabled={!canSendMessage} onClick={() => handleSubmit(inputText)}>
                  Send
                </Button>
              </InputRightElement>
            </InputGroup>
          </Center>
        </Stack>
      </Container>
    </ChakraProvider>
  );
}
Example #26
Source File: index.tsx    From dope-monorepo with GNU General Public License v3.0 4 votes vote down vote up
MarketFilterBar = ({
  setViewCompactCards,
  setOrderBy,
  orderBy,
  setFilterBy,
  filterBy,
  compactSwitchOn,
  setSearchValue,
}: MarketFilterBarProps) => {
  const [sortBy, setSortBy] = useQueryParam('sort_by', sortKeys[1].value);
  const [status, setStatus] = useQueryParam('status', statusKeys[2]);
  const [searchValueParam, setSearchValueParam] = useQueryParam('q', '');

  const debouncedSearchValue = useDebounce<string>(searchValueParam, 250);

  useEffect(() => {
    setSearchValue(debouncedSearchValue);
  }, [debouncedSearchValue, setSearchValue]);

  useEffect(() => {
    setFilterBy(status as FILTERS);
    setOrderBy(sortBy as SearchOrderField);
  }, [sortBy, setOrderBy, status, setFilterBy]);

  const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
    const value = event.target.value;
    setSearchValueParam(value);
    setSearchValue(value);
  };

  const handleStatusChange = (event: ChangeEvent<HTMLSelectElement>) => {
    const value = event.target.value;
    setStatus(value);
    setFilterBy(status as FILTERS);
  };

  const handleSortChange = (event: ChangeEvent<HTMLSelectElement>) => {
    const value = event.target.value;
    setSortBy(value);
    setFilterBy(value as FILTERS);
  };

  const iconPath = '/images/icon';
  const icon = compactSwitchOn ? 'expand' : 'collapse';

  return (
    <Stack
      margin="0"
      spacing="8px"
      width="100%"
      padding="16px"
      background="white"
      borderBottom="2px solid black"
      direction={['column', 'column', 'row']}
    >
      <a
        href="https://dope-wars.notion.site/dope-wars/Dope-Wiki-e237166bd7e6457babc964d1724befb2#d97ecd4b61ef4189964cd67f230c91c5"
        target="wiki"
      >
        <Button fontSize="xs">DOPE NFT FAQ</Button>
      </a>
      <Container>
        <InputGroup>
          <Input
            className="search"
            placeholder="Search…"
            size="sm"
            onChange={handleSearchChange}
            value={searchValueParam}
            _focus={{ boxShadow: '0' }}
          />
          {searchValueParam !== '' && (
            <InputRightElement height="100%">
              <Image
                width="16px"
                src="/images/icon/circle-clear-input.svg"
                alt="Search"
                onClick={() => setSearchValueParam('')}
                cursor="pointer"
              />
            </InputRightElement>
          )}
        </InputGroup>
        <div>
          <Select
            className="status"
            size="sm"
            onChange={handleStatusChange}
            value={filterBy}
            fontSize="xs"
          >
            <option disabled>Status…</option>
            {statusKeys.map((value, index) => (
              <option key={`${value}-${index}`}>{value}</option>
            ))}
          </Select>
        </div>
        <div>
          <Select size="sm" fontSize="xs" onChange={handleSortChange} value={orderBy}>
            <option disabled>Sort By…</option>
            {sortKeys.map(({ label, value }, index) => (
              <option key={`${value}-${index}`} value={value}>
                {label}
              </option>
            ))}
          </Select>
        </div>
        <Button
          className="toggleButton"
          onClick={() => setViewCompactCards(prevState => !prevState)}
        >
          <Image alt="toggle" src={`${iconPath}/${icon}.svg`} />
        </Button>
      </Container>
    </Stack>
  );
}
Example #27
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 #28
Source File: TokenSelect.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
TokenSelect = ({
  onSelectToken: _onSelectToken,
  onClose,
  mode,
}: {
  mode: Mode;
  onClose: () => any;
  onSelectToken: (symbol: string) => any;
}) => {
  const [searchNeedle, setSearchNeedle] = useState("");

  const poolType = usePoolType();

  const noSlippageCurrencies = useNoSlippageCurrencies(poolType);

  const tokenKeys = (() => {
    if (poolType === Pool.ETH) {
      return ["ETH"];
    }

    return searchNeedle === ""
      ? Object.keys(tokens).sort((a, b) => {
          // First items shown last, last items shown at the top!
          const priorityCurrencies = [
            "sUSD",
            "WETH",
            "ETH",
            "DAI",
            "mUSD",
            "USDT",
            "USDC",
          ];

          if (priorityCurrencies.indexOf(a) < priorityCurrencies.indexOf(b)) {
            return 1;
          }
          if (priorityCurrencies.indexOf(a) > priorityCurrencies.indexOf(b)) {
            return -1;
          }

          return 0;
        })
      : Object.keys(tokens).filter((symbol) =>
          symbol.toLowerCase().startsWith(searchNeedle.toLowerCase())
        );
  })();

  const { t } = useTranslation();

  return (
    <Fade>
      <ModalTitleWithCloseButton text={t("Select A Token")} onClose={onClose} />
      <ModalDivider />
      <Box px={4}>
        <InputGroup mb={2}>
          <InputLeftElement
            ml={-1}
            children={<SearchIcon color="gray.300" />}
          />
          <Input
            variant="flushed"
            roundedLeft="0"
            placeholder={t("Try searching for 'USDC'")}
            focusBorderColor="#FFFFFF"
            value={searchNeedle}
            onChange={(event) => setSearchNeedle(event.target.value)}
          />
        </InputGroup>
      </Box>

      <Box px={4}>
        No Slippage:{" "}
        <b>
          {!noSlippageCurrencies
            ? " Loading..."
            : noSlippageCurrencies.map((token: string, index: number) => {
                return (
                  token +
                  (index === (noSlippageCurrencies as string[]).length - 1
                    ? ""
                    : ", ")
                );
              })}
        </b>
      </Box>

      <Box
        pt={2}
        px={4}
        width="100%"
        height={{
          md: poolHasDivergenceRisk(poolType) ? "182px" : "157px",
          base: poolHasDivergenceRisk(poolType) ? "210px" : "170px",
        }}
      >
        <TokenList
          mode={mode}
          tokenKeys={tokenKeys}
          onClick={(symbol) => {
            _onSelectToken(symbol);
            onClose();
          }}
        />
      </Box>
    </Fade>
  );
}