@chakra-ui/react#Tag TypeScript Examples

The following examples show how to use @chakra-ui/react#Tag. 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: ChipInput.test.tsx    From ke with MIT License 6 votes vote down vote up
test('Chip Input backspace on empty input does not break on empty list', () => {
  const handleChange = jest.fn()
  const chipInput = mount(getComponent([], handleChange))

  chipInput.find('input').simulate('keydown', { key: 'Backspace' })

  expect(chipInput.find(Tag).length).toEqual(0)
  expect(handleChange).toBeCalledTimes(0)
})
Example #2
Source File: SelectedFoodItem.tsx    From calories-in with MIT License 6 votes vote down vote up
function SelectedFoodItem({ food, onUnselect }: Props) {
  return (
    <Fade in={true}>
      <Tag
        size="md"
        borderRadius="full"
        variant="outline"
        colorScheme="teal"
        maxWidth="250px"
      >
        <TagLabel>{food.name}</TagLabel>
        <TagCloseButton onClick={() => onUnselect(food)} />
      </Tag>
    </Fade>
  )
}
Example #3
Source File: PaymentMethodTag.tsx    From coindrop with GNU General Public License v3.0 6 votes vote down vote up
PaymentMethodTag: FunctionComponent<Props> = ({ label, iconName, iconSize = "16px", color, tagColorScheme }) => {
    const Icon = paymentMethodIcons[iconName];
    return (
        <Box mx={1} my={1}>
            <Tag size="lg" colorScheme={tagColorScheme}>
                {iconName && (<Icon verticalAlign="top" color={color} boxSize={iconSize} mr={2} />)}
                <TagLabel py={1}>{label}</TagLabel>
            </Tag>
        </Box>
    );
}
Example #4
Source File: FileInput.tsx    From coindrop with GNU General Public License v3.0 6 votes vote down vote up
FileInput = forwardRef<FileInputRef, Props>((props, ref) => {
    const { id, text, accept, onChange } = props;
    return (
        <div>
            <input
                type="file"
                name={id}
                id={id}
                className={styles.input}
                accept={accept}
                onChange={onChange}
                ref={ref}
                data-cy="file-input"
            />
            <label
                className={styles.label}
                htmlFor={id}
            >
                <Tag size="lg" variant="subtle" colorScheme="gray">
                    <TagLeftIcon as={CgSoftwareUpload} />
                    <Text>{text}</Text>
                </Tag>
            </label>
        </div>
    );
})
Example #5
Source File: ChipInput.test.tsx    From ke with MIT License 6 votes vote down vote up
test('Chip Input adds value on blur event', () => {
  fc.assert(
    fc.property(stringArbitrary, (value) => {
      const handleChange = jest.fn()
      const chipInput = mount(getComponent([], handleChange))

      chipInput.find('input').simulate('change', makeChangeEvent(value))
      chipInput.find('input').simulate('blur')

      expect(chipInput.find(Tag).length).toEqual(1)
      expect(chipInput.find(TagLabel).text()).toEqual(value.trim())
      expect(handleChange).toBeCalledWith([value.trim()])
    })
  )
})
Example #6
Source File: ChipInput.test.tsx    From ke with MIT License 6 votes vote down vote up
test('Chip Input validation error', () => {
  fc.assert(
    fc.property(arrayArbitrary, stringArbitrary, stringArbitrary, (content, value, errorText) => {
      const handleChange = jest.fn()
      const validator = (): boolean => false
      const chipInput = mount(getComponent(content, handleChange, validator, errorText))
      chipInput.find('input').simulate('change', makeChangeEvent(value))

      chipInput.find('input').simulate('keydown', { key: 'Enter' })

      expect(chipInput.find(Tag).length).toEqual(content.length)
      expect(chipInput.find('.error').first().text()).toEqual(errorText)
      expect(handleChange).toBeCalledTimes(0)
    })
  )
})
Example #7
Source File: ChipInput.test.tsx    From ke with MIT License 6 votes vote down vote up
test('Chip Input backspace on non-empty input removes last symbol', () => {
  fc.assert(
    fc.property(arrayArbitrary, stringArbitrary, (content, value) => {
      const handleChange = jest.fn()
      const chipInput = mount(getComponent(content, handleChange))
      chipInput.find('input').simulate('change', makeChangeEvent(value))

      chipInput.find('input').simulate('keydown', { key: 'Backspace' })

      expect(chipInput.find(Tag).length).toEqual(content.length)
      expect(handleChange).toBeCalledTimes(0)
    })
  )
})
Example #8
Source File: ChipInput.test.tsx    From ke with MIT License 6 votes vote down vote up
test('Chip Input backspace on empty input removes last chip', () => {
  fc.assert(
    fc.property(arrayArbitrary, (content) => {
      const handleChange = jest.fn()
      const chipInput = mount(getComponent(content, handleChange))

      chipInput.find('input').simulate('keydown', { key: 'Backspace' })

      expect(chipInput.find(Tag).length).toEqual(content.length - 1)
      expect(handleChange).toBeCalledWith(content.slice(0, -1))
    })
  )
})
Example #9
Source File: ChipInput.test.tsx    From ke with MIT License 6 votes vote down vote up
test.each([['Enter'], ['Tab']])('Chip Input adds values on submitKeys pressed', (submitKey) => {
  fc.assert(
    fc.property(stringArbitrary, (value) => {
      const handleChange = jest.fn()
      const chipInput = mount(getComponent([], handleChange))

      chipInput.find('input').simulate('change', makeChangeEvent(value))
      chipInput.find('input').simulate('keydown', { key: submitKey })

      expect(chipInput.find(Tag).length).toEqual(1)
      expect(chipInput.find(TagLabel).text()).toEqual(value.trim())
      expect(handleChange).toBeCalledWith([value.trim()])
    })
  )
})
Example #10
Source File: ChipInput.test.tsx    From ke with MIT License 6 votes vote down vote up
test('Chip Input initial values are rendered', () => {
  fc.assert(
    fc.property(arrayArbitrary, (content) => {
      const chipInput = mount(getComponent(content, jest.fn()))

      expect(chipInput.find(Tag).length).toEqual(content.length)
    })
  )
})
Example #11
Source File: ReactSelectCustomization.tsx    From ke with MIT License 6 votes vote down vote up
export function MultiValue<OptionType>(props: MultiValueProps<OptionType>): JSX.Element {
  const { children, className, cx, getStyles, innerProps, isDisabled, removeProps, data, selectProps } = props

  const { multiValueContainer, multiValueLabel, multiValueRemove } = useStyles()

  const externalClassName = (selectProps as ExtendedProps).componentsClasses?.MultiValue

  return (
    <ClassNames>
      {({ css, cx: emotionCx }) => (
        <Tag data={data} sx={multiValueContainer} {...innerProps} className={classNames(className, externalClassName)}>
          <TagLabel sx={multiValueLabel} className={className}>
            {children}
          </TagLabel>
          <TagCloseButton
            className={emotionCx(
              css(getStyles('multiValueRemove', props)),
              cx(
                {
                  'multi-value__remove': true,
                },
                className
              )
            )}
            isDisabled={isDisabled}
            {...removeProps}
            sx={multiValueRemove}
          />
        </Tag>
      )}
    </ClassNames>
  )
}
Example #12
Source File: CourseCard.tsx    From fresh-coupons with GNU General Public License v3.0 6 votes vote down vote up
function CourseTags({tags}: CourseTagsProps) {
  if (!tags) {
    return <></>
  }

  return (
    <HStack my="2" spacing="3">
      {tags && tags.map(tag => (
        <Tag px="3" key={tag} color={useColorModeValue('gray.600', 'gray.300')}>
          {tag || 'Misc'}
        </Tag>
      ))}
    </HStack>
  )
}
Example #13
Source File: App.tsx    From engine with MIT License 5 votes vote down vote up
App: view = ({
  data = observe.structure.data,
  viewsCount = observe.structure.count.views,
  producersCount = observe.structure.count.producers,
}) => {
  if (!data || !viewsCount || !producersCount) {
    return;
  }

  return (
    <ChakraProvider>
      <SimpleGrid columns={2}>
        <Box bg="gray.100" h="100vh">
          <Tabs>
            <TabList position="relative">
              <Tab>State</Tab>
              <Tab>
                Views <Tag>{viewsCount}</Tag>
              </Tab>
              <Tab>
                Producers <Tag>{producersCount}</Tag>
              </Tab>
              <Tab>Stats</Tab>
            </TabList>
            <TabPanels>
              <TabPanel pr="0">
                <List>
                  <Box overflowY="scroll" h="92vh">
                    <StateTree data={data} />
                  </Box>
                </List>
              </TabPanel>
              <TabPanel pr="0">
                <ViewsTab />
              </TabPanel>
              <TabPanel pr="0">
                <ProducersTab />
              </TabPanel>
              <TabPanel pr="0">
                <StatsTab />
              </TabPanel>
            </TabPanels>
          </Tabs>
        </Box>
        <Box bg="gray.200" borderLeft="solid 1px" borderColor="gray.300">
          <EditElement />
          <ElementDescription />
        </Box>
      </SimpleGrid>
    </ChakraProvider>
  );
}
Example #14
Source File: about.tsx    From portfolio with MIT License 5 votes vote down vote up
Card = (props: CardProps) => {
  const { title, role, skills, period, logo, colorMode, alt } = props;
  return (
    <CardTransition>
      <Box
        px={4}
        py={5}
        borderWidth="1px"
        _hover={{ shadow: "lg" }}
        bg={useColorModeValue("white", "gray.800")}
        position="relative"
        rounded="md"
      >
        <Flex justifyContent="space-between">
          <Flex>
            <Image
              rounded="full"
              w={16}
              h={16}
              objectFit="cover"
              fallbackSrc={placeholder}
              src={logo}
              alt={alt}
            />
            <Stack spacing={2} pl={3} align="left">
              <Heading
                align="left"
                fontSize="xl"
                color={`mode.${colorMode}.career.text`}
              >
                {title}
              </Heading>
              <Heading
                align="left"
                fontSize="sm"
                color={`mode.${colorMode}.career.subtext`}
              >
                {role}
              </Heading>
              <Stack
                spacing={1}
                mt={3}
                isInline
                alignItems="center"
                display={["none", "none", "flex", "flex"]}
              >
                {skills.map(skill => (
                  <Tag size="sm" padding="0 3px" key={skill}>
                    {skill}
                  </Tag>
                ))}
              </Stack>
            </Stack>
          </Flex>
          <Stack display={["none", "none", "flex", "flex"]}>
            <Text fontSize={14} color={`mode.${colorMode}.career.subtext`}>
              {period}
            </Text>
          </Stack>
        </Flex>
        <Stack
          spacing={1}
          mt={3}
          isInline
          alignItems="center"
          display={["flex", "flex", "none", "none"]}
        >
          {skills.map(skill => (
            <Tag size="sm" padding="0 3px" key={skill}>
              {skill}
            </Tag>
          ))}
        </Stack>
      </Box>
    </CardTransition>
  );
}
Example #15
Source File: TrackTopic.tsx    From takeout-app with MIT License 5 votes vote down vote up
TrackTopic: React.FC<Props> = ({ card, topicNav }) => {
  const topic = card?.topic!;
  if (!topic || !card) {
    return (
      <Flex justify="space-between" align="center" w="100%">
        {topicNav}
      </Flex>
    );
  }

  return (
    <VStack spacing="18px">
      <Flex justify="space-between" align="center" w="100%" alignItems="top">
        <HStack flexGrow={1} flexShrink={0} flexBasis={0} alignItems="top" spacing="12px">
          {card.speakers && card.speakers.length > 0 ? (
            <Box w="100%" maxW="64px">
              <SpeakerAvatar speakers={card.speakers} />
            </Box>
          ) : null}
          <Box as="div">
            <Heading as="h2" fontSize="30px" lineHeight="36px" fontWeight="500" mt="-4px">
              {topic.title}
            </Heading>
            <Box mt="2px">
              {topic.labels.map((v, i) => (
                <Tag
                  key={i}
                  variant="solid"
                  size="sm"
                  mr={1}
                  css={{ backgroundColor: Colors.textMuted, color: "#ffffff" }}
                >
                  {v}
                </Tag>
              ))}
            </Box>
            {card.speakers && card.speakers.length > 0 ? (
              <Box>
                {card.speakers.map((s) => (
                  <TrackTopicSpeaker key={s.avatar_url} speaker={s} />
                ))}
              </Box>
            ) : null}
          </Box>
        </HStack>
        <Box>{topicNav}</Box>
      </Flex>
      <Box py="18px" borderTop="1px solid" borderColor={Colors.border} w="100%">
        <Text as="p">{topic.description}</Text>
      </Box>
    </VStack>
  );
}
Example #16
Source File: project-card.tsx    From portfolio with MIT License 4 votes vote down vote up
ProjectCard: React.FC<ProjectCardProps> = ({
  title,
  description,
  logo,
  blurHash,
  link,
  technologies
}) => {
  const textColor = useColorModeValue("gray.500", "gray.200");
  const [isOpen, setIsOpen] = React.useState(false);
  const toggleOpen = () => setIsOpen(!isOpen);

  return (
    <motion.div layout onClick={toggleOpen}>
      <HStack
        p={4}
        bg={useColorModeValue("white", "gray.800")}
        rounded="xl"
        borderWidth="1px"
        borderColor={useColorModeValue("gray.100", "gray.700")}
        w="100%"
        h="100%"
        textAlign="left"
        align="start"
        spacing={4}
        cursor="pointer"
        _hover={{ shadow: "lg" }}
      >
        <LazyImage
          src={logo}
          blurHash={blurHash}
          size="sm"
          width={33}
          height={33}
          layout="fixed"
          rounded="md"
        />
        <VStack align="start" justify="flex-start">
          <VStack spacing={0} align="start">
            <motion.div layout>
              <HStack>
                <Text
                  as={Link}
                  href={link}
                  fontWeight="bold"
                  fontSize="md"
                  noOfLines={1}
                  onClick={e => e.stopPropagation()}
                  isExternal
                >
                  {title}
                </Text>
                <HStack spacing="1">
                  {technologies.map(tech => (
                    <Tag size="sm" colorScheme={getTagColor(tech)}>
                      {tech}
                    </Tag>
                  ))}
                </HStack>
              </HStack>
            </motion.div>
            <AnimatePresence>
              <motion.div
                layout
                initial={{ opacity: 1 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 1 }}
              >
                {!isOpen && (
                  <Text fontSize="sm" color={textColor} noOfLines={{ base: 2 }}>
                    {description}
                  </Text>
                )}
              </motion.div>
            </AnimatePresence>

            <AnimatePresence>
              <motion.div
                layout
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                variants={{ exit: { transition: { staggerChildren: 0.1 } } }}
              >
                {isOpen && (
                  <Text
                    fontSize="sm"
                    color={textColor}
                    // noOfLines={{ base: isOpen ? 5 : 2 }}
                  >
                    {description}
                  </Text>
                )}
              </motion.div>
            </AnimatePresence>
          </VStack>
        </VStack>
      </HStack>
    </motion.div>
  );
}
Example #17
Source File: ContactDialog.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
ContactDialog = ({
    onCancel,
    onDelete,
    onCreate,
    onUpdate,
    onClose,
    onAddressAdd,
    onAddressDelete,
    isOpen,
    modalRef,
    existingContact,
}: ContactDialogProps): JSX.Element => {
    const [firstName, setFirstName] = useState('');
    const [lastName, setLastName] = useState('');
    const [displayName, setDisplayName] = useState('');
    const [currentAddress, setCurrentAddress] = useState('');
    const [hasEdited, setHasEdited] = useBoolean(false);
    const [phones, setPhones] = useState([] as ContactAddress[]);
    const [emails, setEmails] = useState([] as ContactAddress[]);
    const [firstNameError, setFirstNameError] = useState('');
    const isNameValid = (firstNameError ?? '').length > 0;

    useEffect(() => {
        if (!existingContact) return;
        if (existingContact.firstName) setFirstName(existingContact.firstName);
        if (existingContact.lastName) setLastName(existingContact.lastName);
        if (existingContact.displayName) setDisplayName(existingContact.displayName);
        if (existingContact.phoneNumbers) setPhones(existingContact.phoneNumbers);
        if (existingContact.emails) setEmails(existingContact.emails);
    }, [existingContact]);

    const addAddress = (address: string) => {
        const existsPhone = phones.map((e: ContactAddress) => e.address).includes(address);
        const existsEmail = emails.map((e: ContactAddress) => e.address).includes(address);
        if (existsPhone || existsEmail) {
            return showErrorToast({
                id: 'contacts',
                description: 'Address already exists!'
            });
        }

        if (address.includes('@')) {
            setEmails([{ address }, ...emails]);
        } else {
            setPhones([{ address }, ...phones]);
        }

        if (onAddressAdd && existingContact) {
            onAddressAdd(existingContact.id, address);
        }
    };

    const removeAddress = (address: string, addressId: number | null) => {
        if (address.includes('@')) {
            setEmails(emails.filter((e: NodeJS.Dict<any>) => e.address !== address));
        } else {
            setPhones(phones.filter((e: NodeJS.Dict<any>) => e.address !== address));
        }

        if (onAddressDelete && addressId) {
            onAddressDelete(addressId);
        }
    };

    const _onClose = () => {
        setPhones([]);
        setEmails([]);
        setFirstName('');
        setLastName('');
        setDisplayName('');
        setCurrentAddress('');
        setHasEdited.off();

        if (onClose) onClose();
    };

    return (
        <AlertDialog
            isOpen={isOpen}
            leastDestructiveRef={modalRef}
            onClose={() => onClose()}
        >
            <AlertDialogOverlay>
                <AlertDialogContent>
                    <AlertDialogHeader fontSize='lg' fontWeight='bold'>
                        {(existingContact) ? 'Edit Contact' : 'Add a new Contact'}
                    </AlertDialogHeader>

                    <AlertDialogBody>
                        <Text>Add a custom contact to the server's database</Text>
                        <FormControl isInvalid={isNameValid} mt={5}>
                            <FormLabel htmlFor='firstName'>First Name</FormLabel>
                            <Input
                                id='firstName'
                                type='text'
                                value={firstName}
                                placeholder='Tim'
                                onChange={(e) => {
                                    setFirstNameError('');
                                    setFirstName(e.target.value);
                                    if (!hasEdited) {
                                        setDisplayName(`${e.target.value} ${lastName}`.trim());
                                    }
                                }}
                            />
                            {isNameValid ? (
                                <FormErrorMessage>{firstNameError}</FormErrorMessage>
                            ) : null}
                        </FormControl>
                        <FormControl mt={5}>
                            <FormLabel htmlFor='lastName'>Last Name</FormLabel>
                            <Input
                                id='lastName'
                                type='text'
                                value={lastName}
                                placeholder='Apple'
                                onChange={(e) => {
                                    setLastName(e.target.value);
                                    if (!hasEdited) {
                                        setDisplayName(`${firstName} ${e.target.value}`.trim());
                                    }
                                }}
                            />
                        </FormControl>
                        <FormControl mt={5}>
                            <FormLabel htmlFor='lastName'>Display Name</FormLabel>
                            <Input
                                id='displayName'
                                type='text'
                                value={displayName}
                                placeholder='Tim Apple'
                                onChange={(e) => {
                                    setHasEdited.on();
                                    setDisplayName(e.target.value);
                                }}
                            />
                        </FormControl>
                        <FormControl mt={5}>
                            <FormLabel htmlFor='address'>Addresses</FormLabel>
                            <HStack>
                                <Input
                                    id='address'
                                    type='text'
                                    value={currentAddress}
                                    placeholder='Add Address'
                                    onChange={(e) => {
                                        setCurrentAddress(e.target.value);
                                    }}
                                />
                                <IconButton
                                    onClick={() => {
                                        if (!currentAddress || currentAddress.length === 0) return;
                                        addAddress(currentAddress);
                                        setCurrentAddress('');
                                    }}
                                    aria-label='Add'
                                    icon={<AiOutlinePlus />}
                                />
                            </HStack>
                            <Flex flexDirection="row" alignItems="center" justifyContent="flex-start" flexWrap="wrap" mt={2}>
                                {[...phones, ...emails].map(((e: ContactAddress) => {
                                    return (
                                        <Tag
                                            mt={1}
                                            mx={1}
                                            size={'md'}
                                            key={e.address}
                                            borderRadius='full'
                                            variant='solid'
                                        >
                                            <TagLabel>{e.address}</TagLabel>
                                            <TagCloseButton
                                                onClick={() => {
                                                    removeAddress(e.address, (e.id) ? e.id : null);
                                                }}
                                            />
                                        </Tag>
                                    );
                                }))}
                            </Flex>
                        </FormControl>
                    </AlertDialogBody>

                    <AlertDialogFooter>
                        <Button
                            ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                            onClick={() => {
                                if (!existingContact && onCancel) onCancel();
                                if (existingContact && onUpdate) {
                                    existingContact.firstName = firstName;
                                    existingContact.lastName = lastName;
                                    existingContact.displayName = displayName;
                                    onUpdate(existingContact);
                                }
                                _onClose();
                            }}
                        >
                            {(existingContact) ? 'Save & Close' : 'Cancel'}
                        </Button>
                        {(existingContact) ? (
                            <Button
                                ml={3}
                                bg='red'
                                ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                                onClick={() => {
                                    if (onDelete) {
                                        onDelete(Number.parseInt(existingContact.id));
                                    }

                                    _onClose();
                                }}
                            >
                                Delete
                            </Button>
                        ) : null}
                        {(!existingContact) ? (
                            <Button
                                ml={3}
                                bg='brand.primary'
                                ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                                onClick={() => {
                                    if (firstName.length === 0) {
                                        setFirstNameError('Please enter a first name for the contact!');
                                        return;
                                    }

                                    if (onCreate) {
                                        onCreate({
                                            firstName,
                                            lastName,
                                            phoneNumbers: phones,
                                            emails: emails,
                                            displayName,
                                            birthday: '',
                                            avatar: '',
                                            id: '',
                                            sourceType: 'db'
                                        });
                                    }

                                    _onClose();
                                }}
                            >
                                Create
                            </Button>
                        ) : null}
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialogOverlay>
        </AlertDialog>
    );
}
Example #18
Source File: offline-data-card.tsx    From portfolio with MIT License 4 votes vote down vote up
RepositoryCard = (props: RepositoryCardProps) => {
  const {
    key,
    title,
    description,
    cover,
    blurHash,
    technologies,
    url,
    live,
    stars,
    fork,
  } = props;
  const { isOpen, onOpen, onClose } = useDisclosure();

  const handleClick = () => {
    onOpen();
    // window.open(link);
    // if (type == "link" || type == "article") {
    //   window.open(link);
    // } else {
    //   onOpen();
    // }
  };

  const handleLinkClick = (
    e: React.MouseEvent<HTMLParagraphElement, MouseEvent>,
    link: string
  ) => {
    window.open(link);
    e.stopPropagation();
  };

  const transition = { duration: 0.5, ease: [0.43, 0.13, 0.23, 0.96] };

  const thumbnailVariants = {
    initial: { scale: 0.9, opacity: 0 },
    enter: { scale: 1, opacity: 1, transition },
    exit: {
      scale: 0.5,
      opacity: 0,
      transition: { duration: 1.5, ...transition }
    }
  };

  const imageVariants = {
    hover: { scale: 1.1 }
  };

  return (
    <CardTransition>
      <Box onClick={handleClick} cursor="pointer" size="xl">
        <VStack
          //   w="100%"
          rounded="xl"
          borderWidth="1px"
          bg={useColorModeValue("white", "gray.800")}
          borderColor={useColorModeValue("gray.100", "gray.700")}
          _hover={{
            shadow: "lg",
            textDecoration: "none"
          }}
          overflow="hidden"
          align="start"
          spacing={0}
        >
          <Box position="relative" w="100%">
            <MotionBox variants={thumbnailVariants}>
              <MotionBox
                whileHover="hover"
                variants={imageVariants}
                transition={transition}
              >
                <AspectRatio
                  ratio={1.85 / 1}
                  maxW="400px"
                  w="100%"
                  borderBottomWidth="1px"
                  borderColor={useColorModeValue("gray.100", "gray.700")}
                >
                  {/* <Image
                    src={cover}
                    fallback={<Skeleton />}
                    objectFit="cover"
                  /> */}
                  <LazyImage
                    src={cover}
                    blurHash={blurHash}
                  />
                </AspectRatio>
              </MotionBox>
            </MotionBox>
          </Box>

          <VStack py={2} px={[2, 4]} spacing={1} align="start" w="100%">
            <Flex justifyContent={"space-between"} width="100%">
              <Tooltip hasArrow label="Github link" placement="top">
                <HStack>
                  <Icon as={FiGithub} boxSize="0.9em" mt={"1px"} />
                  {/* <Link href={url} isExternal> */}
                  <Text
                    fontSize="sm"
                    noOfLines={1}
                    fontWeight="600"
                    align="left"
                    onClick={e => handleLinkClick(e, url)}
                  >
                    {title}
                  </Text>
                </HStack>
              </Tooltip>
              {/* </Link> */}
              <Flex>
                <Icon as={AiOutlineStar} boxSize="0.9em" mt={"1px"} />
                <Box as="span" ml="1" fontSize="sm">
                  {stars}
                </Box>
              </Flex>
            </Flex>
            <Flex justifyContent={"space-between"} width="100%">
              <Box>
                <HStack spacing="1">
                  {technologies.map(tech => (
                    <Tag size="sm" colorScheme={getTagColor(tech)}>
                      <Text fontSize={["0.55rem", "inherit", "inherit"]}>
                        {tech}
                      </Text>
                    </Tag>
                  ))}
                </HStack>
              </Box>
            </Flex>
            {/* <Flex justifyContent={"space-between"} width="100%">
              <Flex>
                <AiOutlineStar color="teal.300" />
                <Box as="span" ml="1" fontSize="sm">
                  {stars}
                </Box>
              </Flex>
              <Box >
              <Text
                fontSize="xs"
                fontWeight="400"
                color={useColorModeValue("gray.400", "gray.500")}
              >
                {created}
              </Text>
            </Box>
            </Flex> */}
          </VStack>
        </VStack>
        <Modal isOpen={isOpen} onClose={onClose} isCentered allowPinchZoom>
          <ModalOverlay />
          <ModalContent bg="none" maxW={"28rem"} w="auto">
            <ModalBody p={0} rounded="lg" overflow="hidden" bg="none">
              <Center>
                <Image src={cover} rounded="lg" />
                {/* {type == "image" ? (
                <Image src={cover} rounded="lg" />
              ) : (
                <ReactPlayer url={link} controls playing />
              )} */}
              </Center>
            </ModalBody>
          </ModalContent>
        </Modal>
      </Box>
    </CardTransition>
  );
}
Example #19
Source File: live-data-card.tsx    From portfolio with MIT License 4 votes vote down vote up
RepositoryCard = (props: RepositoryCardProps) => {
  const {
    title,
    description,
    language,
    url,
    stargazers_count,
    forks_count,
  } = props;

  const handleLinkClick = (
    e: React.MouseEvent<HTMLParagraphElement, MouseEvent>,
    link: string
  ) => {
    window.open(link);
    e.stopPropagation();
  };

  return (
    <MotionBox whileHover={{ y: -5 }}>
      <Box
        size="xl"
        py={2}
        px={[2, 4]}
        mt={2}
        rounded="xl"
        borderWidth="1px"
        bg={useColorModeValue("white", "gray.800")}
        borderColor={useColorModeValue("gray.100", "gray.700")}
        _hover={{
          shadow: "lg",
          textDecoration: "none"
        }}
      >
        <VStack overflow="hidden" align="start" spacing={1}>
          <VStack spacing={1} align="start" w="100%">
            <Flex
              justifyContent={"space-between"}
              width="100%"
              onClick={e => handleLinkClick(e, url)}
            >
              <Tooltip hasArrow label="Github link" placement="top">
                <HStack cursor={"pointer"}>
                  <Icon as={FiGithub} boxSize="0.9em" mt={"1px"} />
                  <Text
                    fontSize="sm"
                    noOfLines={1}
                    fontWeight="600"
                    align="left"
                    color={"blue.500"}
                  >
                    {title}
                  </Text>
                </HStack>
              </Tooltip>
              <HStack cursor={"pointer"} onClick={e => handleLinkClick(e, url)}>
                {forks_count && (
                  <Box _hover={{ color: "blue.500" }}>
                    <Icon as={BiGitRepoForked} boxSize="0.9em" mt={"1px"} />
                    <Box as="span" ml="1" fontSize="sm">
                      {forks_count}
                    </Box>
                  </Box>
                )}
                <Box _hover={{ color: "blue.500" }}>
                  <Icon as={BiStar} boxSize="0.9em" mt={"1px"} />
                  <Box as="span" ml="1" fontSize="sm">
                    {stargazers_count}
                  </Box>
                </Box>
              </HStack>
            </Flex>
            {language && (
              <Flex justifyContent={"space-between"} width="100%">
                <Box>
                  <HStack spacing="1">
                    <Tag size="sm" colorScheme={getTagColor(language)}>
                      <Text fontSize={["0.55rem", "inherit", "inherit"]}>
                        {language}
                      </Text>
                    </Tag>
                  </HStack>
                </Box>
              </Flex>
            )}
          </VStack>
          <Box>
            <Text color="gray.500" fontSize="sm" noOfLines={2} textAlign="left">
              {description}
            </Text>
          </Box>{" "}
        </VStack>
      </Box>
    </MotionBox>
  );
}
Example #20
Source File: post-card.tsx    From portfolio with MIT License 4 votes vote down vote up
PostCard: React.SFC<PostCardProps> = ({ article }) => {
  const textColor = useColorModeValue("gray.500", "gray.200");
  const devIcon = useColorModeValue(dev, dev2);

  return (
    <CardTransition>
      <VStack
        spacing={1}
        p={4}
        isExternal
        _hover={{ shadow: "md", textDecoration: "none" }}
        borderWidth="1px"
        position="relative"
        rounded="md"
        bg={useColorModeValue("white", "gray.800")}
        align="left"
      >
        {article.external ? (
          <Tooltip hasArrow label="Dev.to" placement="top">
            <Image
              src={devIcon}
              width="2rem"
              height="2rem"
              position="absolute"
              color="#cbd5e0"
              right="0.5rem"
              top="-14px"
            />
          </Tooltip>
        ) : (
          <Tooltip hasArrow label="mahmad.me" placement="top">
            <Box position="absolute" color="#cbd5e0" right="0.5rem" top="-14px">
              <Badge ml="1" variant="solid" colorScheme="blackAlpha">
                Website
              </Badge>
            </Box>
          </Tooltip>
        )}
        <Heading fontSize="lg" align="left" mt={0}>
          {article.external ? (
            <Text as={Link} href={article.link} target="_blank">
              {article.title}
            </Text>
          ) : (
            <Link as={NavLink} to={article.link}>
              {article.title}
            </Link>
          )}
          {article.isNew && (
            <Badge
              ml="1"
              mb="1"
              colorScheme="green"
              fontSize="0.7em"
              lineHeight={1.5}
            >
              New
            </Badge>
          )}
        </Heading>
        <HStack spacing={2} isInline>
          <Tooltip hasArrow label="Published" placement="top">
            <Text fontSize="sm" fontWeight="400" color={textColor}>
              {article.published}
            </Text>
          </Tooltip>
          <Text fontSize="sm" fontWeight="400" color={textColor}>
            •
          </Text>
          <Tooltip hasArrow label="Views" placement="top">
            <Flex alignItems="center">
              <Text
                fontSize="sm"
                noOfLines={1}
                fontWeight="400"
                align="left"
                color={textColor}
              >
                {article.views}
              </Text>
              <Icon as={FaEye} ml={1} color={textColor} />
            </Flex>
          </Tooltip>
          <Text fontSize="sm" fontWeight="600" color={textColor}>
            •
          </Text>
          <Tooltip hasArrow label="Read time" placement="top">
            <Text
              fontSize="sm"
              noOfLines={1}
              fontWeight="400"
              align="left"
              color={textColor}
            >
              {article.readTime}
            </Text>
          </Tooltip>
          <HStack spacing={1} alignItems="center" d={["none", "none", "flex"]}>
            {article.tags.map(tag => (
              <Tag
                size="sm"
                padding="0 3px"
                key={tag}
                colorScheme={getTagColor(tag)}
              >
                {tag}
              </Tag>
            ))}
          </HStack>
        </HStack>
        <HStack spacing={1} alignItems="center" d={["flex", "flex", "none"]}>
          {article.tags.map(tag => (
            <Tag
              size="sm"
              padding="0 3px"
              key={tag}
              colorScheme={getTagColor(tag)}
            >
              {tag}
            </Tag>
          ))}
        </HStack>
        <Text align="left" fontSize="md" noOfLines={4} color={textColor}>
          {article.desc}
        </Text>
      </VStack>
    </CardTransition>
  );
}
Example #21
Source File: ChipInput.tsx    From ke with MIT License 4 votes vote down vote up
ChipInput = forwardRef<HTMLInputElement, ChipInputProps>((props, ref): JSX.Element => {
  const {
    value: inputValue,
    onChange,
    placeholder,
    submitKeys = ['Enter', 'Tab'],
    validator = () => true,
    errorText = 'Invalid value',
    chipClassName,
    inputClassName,
  } = props
  const [chips, setChips] = usePropState<string[]>(inputValue)
  const [value, setValue] = useState<string>('')
  const [error, setError] = useState<string>('')
  // TODO: Исправить
  // eslint-disable-next-line react-hooks/rules-of-hooks
  const inputRef = (ref as RefObject<HTMLInputElement>) ?? useRef<HTMLInputElement>(null)

  const isValid = useCallback(
    (val: string): boolean => {
      if (!validator(val)) {
        setError(errorText)
        return false
      }
      setError('')
      return true
    },
    [errorText, validator]
  )

  const deleteChip = useCallback(
    (index: number): void => {
      const newChips = chips.filter((_, chipIndex) => chipIndex !== index)
      setChips(newChips)
      onChange(newChips)
    },
    [onChange, chips, setChips]
  )

  const finishInput = useCallback((): void => {
    const trimmedValue = value.trim()
    if (trimmedValue && isValid(trimmedValue)) {
      const newChips = [...chips, trimmedValue]
      setChips(newChips)
      onChange(newChips)
      setValue('')
      setError('')
    }
  }, [onChange, chips, setChips, isValid, value])

  const handleKeyDown = useCallback(
    (e: KeyboardEvent<HTMLInputElement>): void => {
      if (submitKeys.includes(e.key)) {
        e.preventDefault()
        finishInput()
      }
      if (e.key === 'Backspace') {
        if (!value && chips.length > 0) {
          e.preventDefault()
          deleteChip(chips.length - 1)
        }
      }
    },
    [chips.length, deleteChip, finishInput, submitKeys, value]
  )

  const { container, input, chip, chipLabel } = useMultiStyleConfig('ChipInput', props)

  return (
    <>
      <Flex __css={container} onClick={() => inputRef?.current?.focus?.()}>
        {chips.map((chipValue: string, index: number) => {
          const key = index
          return (
            <Tag key={key} minWidth={undefined} minHeight={undefined} sx={chip} className={chipClassName}>
              <TagLabel sx={chipLabel} width="100%">
                {chipValue}
              </TagLabel>
              <TagCloseButton onClick={() => deleteChip(key)} />
            </Tag>
          )
        })}
        <Input
          variant="unstyled"
          sx={input}
          value={value}
          onChange={(e: ChangeEvent<HTMLInputElement>): void => setValue(e.target.value)}
          placeholder={placeholder}
          onKeyDown={handleKeyDown}
          isInvalid={!!error}
          ref={inputRef}
          width="auto"
          height="2rem"
          borderRadius="0px"
          onBlur={finishInput}
          className={inputClassName}
        />
      </Flex>
      {error && (
        <Text className="error" fontSize="0.7rem" color="red.400">
          {error}
        </Text>
      )}
    </>
  )
})
Example #22
Source File: StateTree.tsx    From engine with MIT License 4 votes vote down vote up
StateTree: view = ({
  data,
  path,
  _viewId,
  isBodyVisible = observe.views[prop._viewId].data.isBodyVisible,
  updateIsBodyVisible = update.views[prop._viewId].data.isBodyVisible,
}) => {
  if (!data) {
    return null;
  }
  let isRoot = false;
  if (path === undefined) {
    path = "";
    isRoot = true;
  } else {
    if (path === "") {
      path = `${data.name}`;
    } else {
      path = `${path}.${data.name}`;
    }
  }
  const hasChildren = data.children && Object.keys(data.children).length > 0;
  const hasElements =
    (data.elements?.view?.length || 0) +
      (data.elements?.producer?.length || 0) >
    0;
  return (
    <ListItem ml="0" key={_viewId}>
      {!isRoot && hasChildren && (
        <Flex mb="2">
          <Tag
            variant={isBodyVisible ? "solid" : "subtle"}
            cursor="pointer"
            size="sm"
            userSelect="none"
            onClick={() => updateIsBodyVisible.set(!isBodyVisible)}
          >
            <TagLeftIcon>
              {!isBodyVisible && <ChevronDownIcon />}
              {isBodyVisible && <ChevronUpIcon />}
            </TagLeftIcon>
            <TagLabel>{data.name}</TagLabel>
          </Tag>
          {hasElements && (
            <ElementsSummary parentId={_viewId} elements={data.elements} />
          )}
          {path.split(".").length > 1 && (
            <Text fontSize="sm" ml="4" color="gray.500">
              {path}
            </Text>
          )}
        </Flex>
      )}
      {!isRoot && !hasChildren && (
        <Flex mb="2">
          <Flex>
            <Text fontSize="sm">{data.name}</Text>
            {hasElements && (
              <ElementsSummary parentId={_viewId} elements={data.elements} />
            )}
          </Flex>
          {path.split(".").length > 1 && (
            <Text fontSize="sm" ml="4" color="gray.500">
              {path}
            </Text>
          )}
        </Flex>
      )}
      {isRoot && (
        <Text color="gray.500" fontWeight="bold">
          Root
        </Text>
      )}

      <ElementsList
        elements={data.elements}
        parentId={_viewId}
        path={path}
      ></ElementsList>
      {(isBodyVisible || isRoot) && (
        <Children data={data.children} path={path} />
      )}
    </ListItem>
  );
}
Example #23
Source File: Item.tsx    From ksana.in with Apache License 2.0 4 votes vote down vote up
export function Item({ user, data }: IUrlItemProps) {
  const { showAlert, hideAlert } = useAlertContext()
  const [updateId, setUpdateId] = useState<string>('')
  const [updateSlug, setUpdateSlug] = useState<string>('')
  const [isSuccessCopy, setSuccessCopy] = useState<boolean>(false)
  const [isLoadingShare, setLoadingShare] = useState<boolean>(false)
  const [isLoadingSave, setLoadingSave] = useState<boolean>(false)
  const isSupportShare: boolean =
    typeof window !== 'undefined' ? navigator.share !== undefined : false

  const bgBox = useColorModeValue('white', 'gray.800')
  const bgInput = useColorModeValue('blackAlpha.100', 'whiteAlpha.100')

  const showSuccessCopy = () => {
    setSuccessCopy(true)
    setTimeout(() => {
      setSuccessCopy(false)
    }, 2000)
  }

  const handleCopy = async (text: string) => {
    if (navigator.clipboard) {
      await navigator.clipboard.writeText(text)
    } else {
      copy(text)
    }
    showSuccessCopy()
  }

  const handleShare = async (url: string) => {
    if (navigator.share) {
      setLoadingShare(true)
      const jsonRes = await getMeta(url)

      const shareObj = {
        title: jsonRes.title,
        text: jsonRes.description,
        url: url
      }

      navigator
        .share(shareObj)
        // eslint-disable-next-line no-console
        .then(() => setLoadingShare(false))
        .catch((error) => {
          setLoadingShare(false)
          // eslint-disable-next-line no-console
          console.error('Error sharing', error, shareObj)
        })
    }
  }

  const handleClickEdit = async (id: string) => {
    if (updateId === id) {
      setUpdateId('')
      setUpdateSlug('')
    } else {
      setUpdateId(id)
      setUpdateSlug('')
    }
  }

  const handleChangeUpdatedSlug = async (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value
    setUpdateSlug(value)
  }

  const handleClickSave = async () => {
    if (updateSlug) {
      setLoadingSave(true)
      const { error } = await patchSlug({
        id: updateId,
        slug: sanitizeSlug(updateSlug),
        userId: user?.id
      })

      if (error) {
        showAlert({
          title: 'Terjadi galat pada saat memperbarui data',
          message: `Pesan: ${error.message}`,
          onClose: () => {
            hideAlert()
            setLoadingSave(false)
          }
        })
      } else {
        mutate(apiUrlsGet(user?.id))
        setUpdateId('')
        setUpdateSlug('')
        setLoadingSave(false)
      }
    }
  }

  const onConfimDelete = async (id: string) => {
    const { error } = await deleteUrl({ id: id, userId: user?.id })

    if (error) {
      hideAlert()

      showAlert({
        title: 'Terjadi galat pada saat berusaha menghapus data',
        message: `Pesan: ${error.message}`,
        onClose: () => {
          hideAlert()
        }
      })
    }

    hideAlert()
    mutate(apiUrlsGet(user?.id))
  }

  const handleDelete = async (id: string, slug: string) => {
    showAlert({
      title: 'Konfirmasi hapus',
      message: `Apakah kamu yakin untuk menghapus data ${HOME}${slug}? Aksi ini juga akan menghilangkan semua data statistik terkait.`,
      cancelText: 'Batalkan',
      confirmText: 'Ya, hapus',
      onConfirm: () => {
        onConfimDelete(id)
      },
      onClose: hideAlert
    })
  }

  return (
    <Box
      key={data.slug}
      w={'full'}
      bg={bgBox}
      boxShadow={'2xl'}
      rounded={'md'}
      overflow={'hidden'}
      p={{ base: '4', md: '6' }}
    >
      {!!data.is_dynamic && (
        <Tag size="sm" colorScheme="green">
          Tautan dinamis
        </Tag>
      )}
      <Flex alignItems="center" mb="4">
        <Link
          as="a"
          fontSize={{ base: 'md', md: 'lg' }}
          fontWeight="700"
          color="orange.400"
          href={`${HOME}${data.slug}${!!data.is_dynamic ? '/{param}' : ''}`}
          display="block"
        >
          {`/${data.slug}`}
          {!!data.is_dynamic && '/{param}'}
        </Link>
      </Flex>

      {updateId && updateId === data.id && (
        <HStack alignItems="center" mb="4" mt="2">
          <Input
            size="lg"
            name="slug"
            placeholder={'Tulis slug baru'}
            bg={bgInput}
            border={0}
            value={updateSlug}
            onChange={handleChangeUpdatedSlug}
          />

          <IconButton
            onClick={handleClickSave}
            aria-label="Simpan slug"
            size="lg"
            bg="orange.400"
            borderRadius="md"
            isLoading={isLoadingSave}
            icon={<HiSave color="#FFF" />}
          />
        </HStack>
      )}

      <Text fontSize="small" color="gray.400" display="block" mb="2">
        {data.real_url}
      </Text>
      <Text fontSize="small" color="gray.400">
        <Text as="span" fontWeight="bold" color="orange.400">
          {new Intl.NumberFormat('id-ID').format(data.hit)}
        </Text>
        {` `} kunjungan
      </Text>
      <HStack spacing={2} mt={4} flexWrap="wrap">
        <IconButton
          onClick={() => {
            handleCopy(`${HOME}${data.slug}`)
          }}
          aria-label="Copy"
          variant="ghost"
          borderRadius="md"
          icon={isSuccessCopy ? <HiCheck color="#48BB78" /> : <HiDuplicate color="#ED8936" />}
        />
        {isSupportShare ? (
          <IconButton
            onClick={() => {
              handleShare(`${HOME}${data.slug}`)
            }}
            aria-label="Copy"
            variant="ghost"
            borderRadius="md"
            isLoading={isLoadingShare}
            icon={<HiShare color="#ED8936" />}
          />
        ) : (
          <SharePopover url={`${HOME}${data.slug}`} />
        )}
        <IconButton
          onClick={() => {
            handleClickEdit(data.id)
          }}
          aria-label="Ubah"
          variant="ghost"
          borderRadius="md"
          icon={<HiPencil color="#ED8936" />}
        />
        <IconButton
          onClick={() => {
            handleDelete(data.id, data.slug)
          }}
          aria-label="Hapus"
          variant="ghost"
          borderRadius="md"
          icon={<HiTrash color="#ED8936" />}
        />
      </HStack>
    </Box>
  )
}
Example #24
Source File: notebook-post.tsx    From portfolio with MIT License 4 votes vote down vote up
NotebookPost: React.SFC<PostProps> = () => {
  const textColor = useColorModeValue("gray.500", "gray.200");
  const post = articles[4];

  return (
    <>
      <VStack mt={0} mb={6} spacing={1} align="start">
        <Heading as="h1" fontSize="3xl" lineHeight="shorter" fontWeight="bold">
          {post.title}
        </Heading>
        <Divider
          orientation="horizontal"
          opacity={1}
          borderBottomWidth={0}
          height={"1px"}
          bg={"gray.200"}
        />
      </VStack>
      <Flex
        justifyContent={"space-between"}
        flexDirection={["column", "row", "row"]}
      >
        <HStack spacing={2} isInline>
          <Text fontSize="sm" fontWeight="400" color={textColor}>
            {post.published}
          </Text>
          <Text fontSize="sm" fontWeight="400" color={textColor}>
            •
          </Text>
          <Tooltip hasArrow label="Views" placement="top">
            <Flex alignItems="center">
              <Text
                fontSize="sm"
                noOfLines={1}
                fontWeight="400"
                align="left"
                color={textColor}
              >
                {post.views}
              </Text>
              <Icon as={FaEye} ml={1} color={textColor} />
            </Flex>
          </Tooltip>

          <Text fontSize="sm" fontWeight="600" color={textColor}>
            •
          </Text>
          <Tooltip hasArrow label="Read time" placement="top">
            <Text
              fontSize="sm"
              noOfLines={1}
              fontWeight="400"
              align="left"
              color={textColor}
            >
              {post.readTime}
            </Text>
          </Tooltip>
        </HStack>
        <HStack spacing={1} alignItems="center">
          {post.tags.map(tag => (
            <Tag
              size="sm"
              padding="0 3px"
              key={tag}
              colorScheme={getTagColor(tag)}
            >
              {tag}
            </Tag>
          ))}
        </HStack>
      </Flex>
      <HStack align="end" mt={5}>
        <Link href={post.live} isExternal>
          <Button
            ml={2}
            variant="outline"
            size={["sm"]}
            color={useColorModeValue("green.600", "green.200")}
            bg={useColorModeValue("white", "gray.800")}
            leftIcon={<BiLinkExternal size={18} />}
          >
            Demo
          </Button>
        </Link>
        <Link href={post.github_url} isExternal>
          <Button
            ml={2}
            variant="outline"
            size={["sm"]}
            color={useColorModeValue("green.600", "green.200")}
            bg={useColorModeValue("white", "gray.800")}
            leftIcon={<FiGithub size={18} />}
          >
            Github link
          </Button>
        </Link>
      </HStack>

      <Box height={["35vh", "45vh", "55vh", "70vh"]} marginTop={5}>
        <Carousel images={post.images} />
      </Box>
      <VStack spacing={5} align={"start"} mt={6}>
        <Header fontSize={"xl"} mt={0} mb={0}>
          What will you learn?
        </Header>
        <Box fontSize="md">
          <UnorderedList textAlign="left" paddingLeft={5} m={0}>
            <ListItem>How to create a CRUD app with react</ListItem>
            <ListItem>How to create a responsive app using ChakraUi</ListItem>
            <ListItem>How to use animations with framer-motion</ListItem>
            <ListItem>How to create slider with framer-motion</ListItem>
          </UnorderedList>
        </Box>
      </VStack>
      <VStack spacing={5} align={"start"} mt={6}>
        <Header fontSize={"xl"} mt={0} mb={0}>
          Built with
        </Header>
        <Box fontSize="md">
          <UnorderedList textAlign="left" paddingLeft={5} m={0}>
            <ListItem>
              Programming language -{" "}
              <Link
                href="https://www.typescriptlang.org/"
                isExternal
                color={"blue.500"}
              >
                Typescript
              </Link>
            </ListItem>
            <ListItem>
              Front-end library -{" "}
              <Link
                href="https://github.com/facebook/react/"
                isExternal
                color={"blue.500"}
              >
                React
              </Link>
            </ListItem>
            <ListItem>
              UI components -{" "}
              <Link href="https://chakra-ui.com/" isExternal color={"blue.500"}>
                Chakra-ui
              </Link>
            </ListItem>
            <ListItem>
              Animation library -{" "}
              <Link
                href="https://www.framer.com/motion/"
                isExternal
                color={"blue.500"}
              >
                Framer motion
              </Link>
            </ListItem>
            <ListItem>
              Notes display -{" "}
              <Link
                href="https://github.com/tsuyoshiwada/react-stack-grid"
                isExternal
                color={"blue.500"}
              >
                react-stack-grid
              </Link>
            </ListItem>
            <ListItem>
              Forms Validation -{" "}
              <Link
                href="https://react-hook-form.com/"
                isExternal
                color={"blue.500"}
              >
                React hook form
              </Link>
            </ListItem>
            <ListItem>
              Icons -{" "}
              <Link
                href="https://react-icons.github.io/react-icons/"
                isExternal
                color={"blue.500"}
              >
                React icons
              </Link>
            </ListItem>
            <ListItem>
              Images placeholder -{" "}
              <Link href="https://blurha.sh/" isExternal color={"blue.500"}>
                blurhash
              </Link>
            </ListItem>
            <ListItem>
              Progressive image loading -{" "}
              <Link
                href="https://github.com/FormidableLabs/react-progressive-image"
                isExternal
                color={"blue.500"}
              >
                react-progressive-image
              </Link>
            </ListItem>
          </UnorderedList>
        </Box>
      </VStack>
    </>
  );
}