theme-ui#Box TypeScript Examples

The following examples show how to use theme-ui#Box. 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: index.tsx    From slice-machine with Apache License 2.0 6 votes vote down vote up
function Grid<T>({
  elems,
  gridTemplateMinPx = "320px",
  renderElem,
  defineElementKey,
}: GridProps<T>): JSX.Element {
  return (
    <Box
      as="section"
      sx={{
        display: "grid",
        gridTemplateColumns: `repeat(auto-fill, minmax(${gridTemplateMinPx}, 1fr))`,
        gridGap: "16px",
        pt: 2,
        mb: 3,
      }}
    >
      {elems.map((elem: T | undefined, i: number) =>
        elem ? (
          <span key={`${defineElementKey(elem)}-${i + 1}`}>
            {renderElem(elem, i)}
          </span>
        ) : null
      )}
    </Box>
  );
}
Example #2
Source File: Error.tsx    From nft-market with MIT License 6 votes vote down vote up
Error = () => {
  return (
    <Flex>
      <Image sx={{ width: 100, height: 100 }} src="/icons/icon-512x512.png" />
      <Box mt={4} ml={4}>
        <Heading as="h2">Metamask not installed</Heading>
        <Text mt={2}>
          Go to{' '}
          <Link href="https://metamask.io" target="_blank">
            https://metamask.io
          </Link>{' '}
          to install it.
        </Text>
      </Box>
    </Flex>
  )
}
Example #3
Source File: site-status-dot.tsx    From desktop with MIT License 6 votes vote down vote up
export function SiteStatusDot({ status, ...rest }: IProps): JSX.Element {
  const displayStatus = getSiteDisplayStatus(status)

  return (
    <Box
      sx={{
        width: 8,
        height: 8,
        borderRadius: 5,
        flexShrink: 0,
        ...(displayStatus === SiteDisplayStatus.Stopped && {
          border: `grey`,
        }),
        ...(displayStatus === SiteDisplayStatus.Starting && {
          backgroundColor: `purple.40`,
          boxShadow: `0 0 0 0 rgba(217, 186, 232, 1)`,
          animation: `${pulse} 2s infinite`,
        }),
        ...(displayStatus === SiteDisplayStatus.Running && {
          backgroundColor: `green.40`,
        }),
        ...(displayStatus === SiteDisplayStatus.Errored && {
          backgroundColor: `orange.70`,
        }),
      }}
      {...rest}
    />
  )
}
Example #4
Source File: AgentAvailability.tsx    From chat-window with MIT License 6 votes vote down vote up
AgentAvailability = ({
  hasAvailableAgents,
  agentAvailableText,
  agentUnavailableText,
}: {
  hasAvailableAgents: boolean;
  agentAvailableText: string;
  agentUnavailableText: string;
}) => {
  return (
    <Flex
      px={20}
      py={1}
      sx={{
        bg: 'lighter',
        borderTop: '1px solid rgba(230, 230, 230, 0.25)',
        alignItems: 'center',
      }}
    >
      <Box
        mr={2}
        sx={{
          height: 8,
          width: 8,
          bg: hasAvailableAgents ? 'green' : 'muted',
          border: '1px solid #fff',
          borderRadius: '50%',
        }}
      ></Box>
      <Text sx={{color: 'offset', fontSize: 12}}>
        {hasAvailableAgents ? agentAvailableText : agentUnavailableText}
      </Text>
    </Flex>
  );
}
Example #5
Source File: UserMenu.tsx    From nft-market with MIT License 6 votes vote down vote up
UserMenu = () => {
  const { user, isAuthenticated } = useAppState()

  const history = useHistory()

  return (
    <Flex sx={{ ml: 'auto', justifySelf: 'flex-end' }}>
      {isAuthenticated && user && (
        <>
          <Box sx={{ display: ['none', 'block'] }}>
            <Heading sx={{ p: 0, color: 'white' }} as="h4">
              {toShort(user.address)}
            </Heading>
            <Heading sx={{ p: 0, mt: 1, textAlign: 'right', color: 'white' }} as="h5">
              {EtherSymbol}
              {user.balance}
            </Heading>
          </Box>
          <Box
            onClick={() => {
              history.push('/profile')
            }}
            sx={{
              cursor: 'pointer',
              ml: [0, 3],
              height: 30,
              width: 30,
              borderRadius: '100%',
            }}
          >
            <Identicon size={30} address={user.address} />
          </Box>
        </>
      )}
    </Flex>
  )
}
Example #6
Source File: ChatMessage.tsx    From chat-window with MIT License 6 votes vote down vote up
PopupChatMessage = ({message}: Props) => {
  const {body, user, type} = message;
  const isBot = type === 'bot';
  const identifer = isBot ? 'Bot' : getAgentIdentifier(user);

  return (
    <Box pr={0} pl={0} pb={2}>
      <Flex
        sx={{justifyContent: 'flex-start', alignItems: 'center', width: '100%'}}
      >
        <SenderAvatar name={identifer} user={user} isBot={isBot} />

        <ChatMessageBody
          sx={{
            px: 3,
            py: 3,
            color: 'text',
            bg: 'background',
            whiteSpace: 'pre-wrap',
            flex: 1,
            border: '1px solid rgb(245, 245, 245)',
            boxShadow: 'rgba(35, 47, 53, 0.09) 0px 2px 8px 0px',
            maxWidth: '84%',
          }}
          content={body}
        />
      </Flex>
    </Box>
  );
}
Example #7
Source File: ContractDetails.tsx    From nft-market with MIT License 6 votes vote down vote up
ContractDetails = () => {
  const { contractDetails } = useAppState()
  return (
    <Box>
      <Heading as="h1">NFT Contract Details</Heading>
      <Grid my={3}>
        {contractDetails &&
          Object.keys(contractDetails).map(a => (
            <Text key={a}>
              {a}:{' '}
              <Text variant="text.bold" as="span">
                {contractDetails[a as keyof ContractPropsDetails]}
              </Text>
            </Text>
          ))}
      </Grid>
    </Box>
  )
}
Example #8
Source File: onboarding.tsx    From slice-machine with Apache License 2.0 6 votes vote down vote up
StepIndicator = ({
  current,
  maxSteps,
}: {
  current: number;
  maxSteps: number;
}) => {
  const columns = Array(maxSteps).fill(1);
  return (
    <Box sx={{ width: "40%" }}>
      <Grid gap={2} columns={maxSteps}>
        {columns.map((_, i) => (
          <Box
            key={`box-${i + 1}`}
            sx={{
              bg: i <= current ? "primary" : "muted",
              borderRadius: "10px",
              height: "2px",
            }}
          />
        ))}
      </Grid>
    </Box>
  );
}
Example #9
Source File: Gallery.tsx    From nft-market with MIT License 5 votes vote down vote up
Gallery = () => {
  const { user, tokensOnSale } = useAppState()
  const updateTokensOnSale = useAppState(
    useCallback(({ updateTokensOnSale }) => updateTokensOnSale, [])
  )

  const [order, setOrder] = useState<StateOrder>('alpha')

  useEffect(() => {
    updateTokensOnSale()
  }, [updateTokensOnSale])

  return (
    <Box>
      <Heading as="h1">Marketplace</Heading>
      <Flex sx={{ alignItems: 'center' }} mb={3}>
        <Heading as="h3" sx={{ color: 'lightGray' }}>
          Order:
        </Heading>
        <Flex ml={3}>
          <Button
            mr={2}
            onClick={() => setOrder('alpha')}
            variant="filter"
            disabled={order === 'alpha'}
          >
            Alphabetically
          </Button>
          <Button onClick={() => setOrder('price')} variant="filter" disabled={order === 'price'}>
            Price
          </Button>
        </Flex>
      </Flex>
      <Grid gap={4} columns={['1fr 1fr', '1fr 1fr', '1fr 1fr 1fr']}>
        {tokensOnSale
          ?.sort((a, b) =>
            order === 'alpha'
              ? BigNumber.from(a.id)
                  .toString()
                  .localeCompare(BigNumber.from(b.id).toString(), undefined, { numeric: true })
              : Number(utils.formatEther(a.price.sub(b.price)))
          )
          .map((i, index) => (
            <Token onBuy={!user?.ownedTokens.find(t => t.id === i.id)} token={i} key={index} />
          ))}
      </Grid>
    </Box>
  )
}
Example #10
Source File: index.tsx    From slice-machine with Apache License 2.0 5 votes vote down vote up
VarationsPopover: React.FunctionComponent<{
  buttonSx?: ThemeUICSSObject;
  defaultValue?: Models.VariationSM;
  // eslint-disable-next-line @typescript-eslint/ban-types
  onNewVariation?: Function;
  variations: ReadonlyArray<Models.VariationSM>;
  onChange: (selected: Models.VariationSM) => void;
}> = ({ buttonSx, defaultValue, variations, onNewVariation, onChange }) => {
  const [isOpen, setIsOpen] = useState(false);
  const [current, setCurrent] = useState<Models.VariationSM>(
    defaultValue || variations[0]
  );

  useEffect(() => {
    if (defaultValue) {
      setCurrent(defaultValue);
    }
  }, [defaultValue]);

  const TRANSITION_DURATION = 200; //ms
  const handleChange = function (variation: Models.VariationSM) {
    setIsOpen(false);
    setCurrent(variation);
    setTimeout(() => onChange(variation), TRANSITION_DURATION + 10); // time to properly close the popover with transition
  };

  const MenuItemAction = (
    <Box sx={{ p: 2 }}>
      <Button
        variant="transparent"
        sx={{ color: "text" }}
        onClick={() => {
          setIsOpen(false);
          if (onNewVariation) {
            onNewVariation();
          }
        }}
      >
        + Add new variation
      </Button>
    </Box>
  );

  return (
    <div>
      <Popover
        align="start"
        isOpen={isOpen}
        onClickOutside={() => setIsOpen(false)}
        positions={["bottom"]}
        padding={10}
        content={() => (
          <MenuList
            defaultValue={current}
            variations={variations}
            onChange={handleChange}
            MenuItemAction={onNewVariation ? MenuItemAction : undefined}
          />
        )}
        containerClassName={"variationSelectorContainer"}
      >
        <Button
          sx={{
            fontSize: 14,
            p: 2,
            pl: 3,
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            ...buttonSx,
          }}
          variant="secondary"
          onClick={() => setIsOpen(!isOpen)}
        >
          {current.name} <RiArrowDropDownLine size="24px" />
        </Button>
      </Popover>
    </div>
  );
}
Example #11
Source File: Header.tsx    From nft-market with MIT License 5 votes vote down vote up
Header = () => {
  const history = useHistory()
  const location = useLocation()

  const { user, isAuthenticated } = useAppState()

  return (
    <Box bg="black">
      <Flex sx={{ alignItems: 'center', p: 3 }} as="nav">
        <Image
          onClick={() => {
            history.push('/')
          }}
          sx={{ width: 50, cursor: 'pointer' }}
          src="/static/logo.png"
        />
        <Heading sx={{ ml: [1, 2], color: 'white' }} as="h4">
          ERC721 Marketplace{' '}
          <Text sx={{ display: ['none', 'block'] }}>+ OpenSea.io on Rinkeby Network</Text>
        </Heading>
        <UserMenu />
      </Flex>
      {isAuthenticated && user && (
        <Flex bg="midGray" py={3} sx={{ justifyContent: 'center' }}>
          <NavLink
            sx={{
              pointerEvents: location.pathname === '/' ? 'none' : 'visible',
              color: location.pathname === '/' ? 'green' : 'white',
            }}
            onClick={() => history.push('/')}
          >
            Marketplace
          </NavLink>
          <Box sx={{ width: 50 }} />
          <NavLink
            sx={{
              pointerEvents: location.pathname === '/profile' ? 'none' : 'visible',
              color: location.pathname === '/profile' ? 'green' : 'white',
            }}
            onClick={() => history.push('/profile')}
          >
            Profile
          </NavLink>
        </Flex>
      )}
    </Box>
  )
}
Example #12
Source File: MenuList.tsx    From slice-machine with Apache License 2.0 5 votes vote down vote up
MenuList: React.FunctionComponent<{
  defaultValue: Models.VariationSM;
  variations: ReadonlyArray<Models.VariationSM>;
  onChange: (selected: Models.VariationSM) => void;
  MenuItemAction?: React.ReactElement;
}> = ({ defaultValue, variations, MenuItemAction, onChange }) => {
  return (
    <Box
      sx={{
        py: 0,
        flexDirection: "column",
        backgroundColor: "headSection",
        overflow: "auto",
        display: "flex",
        borderRadius: "4px",
        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
        border: (t) => `1px solid ${t.colors?.borders}`,
        boxShadow: "0 10px 10px rgba(0, 0, 0, 0.05)",
        maxHeight: 340,
        minWidth: 250,
        maxWidth: 350,
        color: "text",
      }}
    >
      <Text
        sx={{
          p: 3,
          fontSize: 12,
          color: "textClear",
        }}
      >
        {variations.length} VARIATION{variations.length > 1 ? "S" : ""}
      </Text>
      <Flex sx={{ p: 0, flexDirection: "column" }}>
        {variations.map((v) => {
          return (
            <MenuItem
              key={v.id}
              value={v}
              isActive={v.id === defaultValue.id}
              onClick={onChange}
            />
          );
        })}
        {MenuItemAction ? MenuItemAction : null}
      </Flex>
    </Box>
  );
}
Example #13
Source File: AddComment.tsx    From use-comments with MIT License 5 votes vote down vote up
AddComment = ({ onSubmit }: AddCommentProps) => {
  const [username, setUsername] = useState('');
  const [comment, setComment] = useState('');
  const [added, setAdded] = useState(false);

  return (
    <Box
      as="form"
      onSubmit={e => {
        console.log({ e });
        e.preventDefault();
        onSubmit({ content: comment, author: username });
        setAdded(true);
        setComment('');
        setUsername('');
      }}
    >
      <Label htmlFor="username">Username</Label>
      <Input
        name="username"
        id="username"
        placeholder="Jon Doe"
        value={username}
        onChange={e => setUsername(e.target.value)}
        sx={{ mb: 3 }}
        autoComplete="off"
      />
      <Label htmlFor="comment">Comment</Label>
      <Textarea
        name="comment"
        id="comment"
        rows={2}
        placeholder="Tell me what you think ?"
        value={comment}
        onChange={e => setComment(e.target.value)}
        sx={{ mb: 3, fontFamily: 'body' }}
      />
      <Button
        type="submit"
        sx={{
          mb: 3,
          ...((!username || !comment) && {
            pointerEvents: 'none',
            opacity: '0.5',
          }),
        }}
        disabled={!username || !comment}
      >
        Add comment
      </Button>
      {added && (
        <Message
          variant="primary"
          sx={{
            fontSize: '0.8em',
          }}
        >
          Thanks for your comment! ? Your comment status is{' '}
          <i>waiting for approval</i>. Comments on this website need to be
          approved before they are visible to others.
        </Message>
      )}
      <Divider />
    </Box>
  );
}
Example #14
Source File: ChatMessage.tsx    From chat-window with MIT License 5 votes vote down vote up
ChatMessage = ({
  message,
  isMe,
  companyName,
  isLastInGroup,
  shouldDisplayTimestamp,
}: Props) => {
  const {body, created_at, user, type, attachments = []} = message;
  const created = created_at ? dayjs.utc(created_at) : null;
  const timestamp = created ? formatRelativeTime(created) : null;
  const isBot = type === 'bot';
  const defaultBotName = companyName || 'Bot';
  const identifer = isBot ? defaultBotName : getAgentIdentifier(user);

  if (isMe) {
    return (
      <Box pr={0} pl={4} pb={isLastInGroup ? 3 : 2}>
        <Flex sx={{justifyContent: 'flex-end'}}>
          <ChatMessageBody
            className="Text--white"
            sx={{
              color: 'background',
              bg: 'primary',
              whiteSpace: 'pre-wrap',
            }}
            content={body}
            attachments={attachments}
          />
        </Flex>
        {shouldDisplayTimestamp && (
          <Flex m={1} sx={{justifyContent: 'flex-end'}}>
            <Text sx={{color: 'gray'}}>
              {timestamp ? `Sent ${timestamp}` : 'Sending...'}
            </Text>
          </Flex>
        )}
      </Box>
    );
  }

  return (
    <Box pr={4} pl={0} pb={isLastInGroup ? 3 : 2}>
      <Flex sx={{justifyContent: 'flex-start', alignItems: 'center'}}>
        <SenderAvatar name={identifer} user={user} isBot={isBot} />

        <ChatMessageBody
          sx={{
            color: 'text',
            bg: 'rgb(245, 245, 245)',
            whiteSpace: 'pre-wrap',
          }}
          content={body}
          attachments={attachments}
        />
      </Flex>
      {shouldDisplayTimestamp && (
        <Flex m={1} sx={{justifyContent: 'flex-start'}}>
          <Text sx={{color: 'gray'}}>
            {identifer} · Sent {timestamp}
          </Text>
        </Flex>
      )}
    </Box>
  );
}
Example #15
Source File: index.tsx    From slice-machine with Apache License 2.0 5 votes vote down vote up
Col: React.FC<ColProps> = ({ children, cols = 2 }) => (
  <Box sx={{ flex: `0 ${100 / cols - 1}%`, mb: 1 }}>{children}</Box>
)
Example #16
Source File: QuickReplies.tsx    From chat-window with MIT License 5 votes vote down vote up
QuickReplies = ({replies, onSelect}: Props) => {
  if (!replies || !replies.length) {
    return null;
  }

  return (
    <Flex
      pb={5}
      sx={{
        justifyContent: 'flex-end',
        alignItems: 'flex-end',
        flexDirection: 'column',
      }}
    >
      {replies.map((reply) => {
        const {text, action} = reply;

        return (
          <motion.div
            key={action}
            initial={{opacity: 0, x: -2}}
            animate={{opacity: 1, x: 0}}
            transition={{duration: 0.2, ease: 'easeIn'}}
          >
            <Box key={action} mb={2}>
              <Button
                variant="secondary"
                px={2}
                py={1}
                sx={{maxWidth: 200, textAlign: 'left'}}
                onClick={() => onSelect(reply)}
              >
                {text}
              </Button>
            </Box>
          </motion.div>
        );
      })}
    </Flex>
  );
}
Example #17
Source File: index.tsx    From slice-machine with Apache License 2.0 5 votes vote down vote up
Header: React.FunctionComponent<PropTypes> = ({
  title,
  Model,
  variation,
  handleScreenSizeChange,
  size,
}) => {
  return (
    <Box
      sx={{
        p: 3,
        display: "grid",
        gridTemplateColumns: "repeat(3, 1fr)",
        gridTemplateRows: "1fr",
        borderBottom: "1px solid #F1F1F1",
      }}
    >
      <Flex
        sx={{
          alignItems: "center",
        }}
      >
        <Text mr={2}>{title}</Text>
        {Model.variations.length > 1 ? (
          <VarationsPopover
            buttonSx={{ p: 1 }}
            defaultValue={variation}
            variations={Model.variations}
            onChange={(v) => redirect(Model, v, true)}
          />
        ) : null}
      </Flex>
      <Flex
        sx={{
          alignItems: "center",
          justifyContent: "space-around",
        }}
      >
        <ScreenSizes size={size} onClick={handleScreenSizeChange} />
      </Flex>
    </Box>
  );
}
Example #18
Source File: ModalMapSelect.tsx    From HappyIslandDesigner with MIT License 5 votes vote down vote up
export default function ModalMapSelect(){
  const [modalIsOpen,setIsOpen] = useState(false);
  function openModal() {
    setIsOpen(true);
  }

  function afterOpenModal() {

  }

  function closeModal(){
    if (!isMapEmpty())
      setIsOpen(false);
  }

  useEffect(() => {
    Modal.setAppElement('body');
  }, []);

  const refCallback = useBlockZoom();

  return (
    <div>
      <button id="open-map-select" style={{display: 'none'}} onClick={openModal}>Open Modal</button>
      <button id="close-map-select" style={{display: 'none'}} onClick={closeModal}>Open Modal</button>
      <Modal
        isOpen={modalIsOpen}
        closeTimeoutMS={200} // keep in sync with modal.scss
        onAfterOpen={afterOpenModal}
        onRequestClose={closeModal}
        style={customStyles}
        sx={{}}
        contentLabel="Example Modal"
      >
        <Flex
          ref={refCallback}
          p={3}
          sx={{
            backgroundColor : colors.paper.cssColor,
            border: 0,
            borderRadius: 8,
            flexDirection: 'column',
            overflow: 'auto',
            //borderRadius: 60,
            //minWidth: 260,
          }}>
          <Box p={2} sx={{
            backgroundColor: colors.level3.cssColor,
            borderRadius: '30px 4px 4px 30px',
          }}>
            <Image variant='block' sx={{maxWidth: 150}} src='static/img/nook-inc-white.png'/>
          </Box>
          <IslandLayoutSelector />
          <Box p={3} sx={{
            backgroundColor: colors.level3.cssColor,
            borderRadius: '4px 30px 30px 4px',
          }} />
        </Flex>
      </Modal>
    </div>
  );
}
Example #19
Source File: Token.tsx    From nft-market with MIT License 4 votes vote down vote up
Token = ({ token, isOnSale, onTransfer, onBuy, onSale }: TokenCompProps) => {
  const [transfer, setTransfer] = useState<boolean>(false)
  const [onSaleActive, setOnSale] = useState<boolean>(false)
  const [address, setAddress] = useState<string>('')
  const [price, setPrice] = useState<string>('')
  const { user, ethPrice, contractDetails, transferToken, buyToken, setTokenSale } = useAppState()

  const onTransferClick = async (e: FormEvent | MouseEvent) => {
    e.preventDefault()
    if (onTransfer && utils.isAddress(address)) {
      transferToken(token.id, address)
      setTransfer(false)
    }
  }

  const onBuyClick = (e: MouseEvent) => {
    e.preventDefault()
    onBuy && buyToken(token.id, token.price)
  }

  const onSaleClick = async (e: MouseEvent) => {
    e.preventDefault()
    if (!onSale) return
    try {
      await setTokenSale(token.id, utils.parseEther(price), true)
      setOnSale(false)
    } catch (e) {
      throw e
    }
  }

  const { data: owner } = useSWR(token.id, fetchOwner)
  const { data } = useSWR(`${METADATA_API}/token/${token.id}`, fetcherMetadata)

  const tokenPriceEth = formatPriceEth(token.price, ethPrice)

  if (!data)
    return (
      <Card variant="nft">
        <Spinner />
      </Card>
    )

  if (!data.name) return null

  return (
    <Card variant="nft">
      <Image
        sx={{ width: '100%', bg: 'white', borderBottom: '1px solid black' }}
        src={data.image}
      />
      <Box p={3} pt={2}>
        <Heading as="h2">{data.name}</Heading>
        <Divider variant="divider.nft" />
        <Box>
          <Text sx={{ color: 'lightBlue', fontSize: 1, fontWeight: 'bold' }}>Price</Text>
          <Heading as="h3" sx={{ color: 'green', m: 0, fontWeight: 'bold' }}>
            {constants.EtherSymbol} {Number(utils.formatEther(token.price)).toFixed(2)}{' '}
            <Text sx={{ color: 'navy' }} as="span" variant="text.body">
              ({tokenPriceEth})
            </Text>
          </Heading>
          {owner && typeof owner === 'string' && !onTransfer && (
            <Box mt={2}>
              <Text as="p" sx={{ color: 'lightBlue', fontSize: 1, fontWeight: 'bold' }}>
                Owner
              </Text>
              <NavLink
                target="_blank"
                href={`https://rinkeby.etherscan.io/address/${owner}`}
                variant="owner"
                style={{
                  textOverflow: 'ellipsis',
                  width: '100%',
                  position: 'relative',
                  overflow: 'hidden',
                }}
              >
                {toShort(owner)}
              </NavLink>
            </Box>
          )}
          <Box mt={2}>
            <NavLink
              target="_blank"
              href={`https://testnets.opensea.io/assets/${contractDetails?.address}/${token.id}`}
              variant="openSea"
            >
              View on Opensea.io
            </NavLink>
          </Box>
        </Box>

        {onTransfer && (
          <Flex mt={3} sx={{ justifyContent: 'center' }}>
            {transfer && (
              <Box sx={{ width: '100%' }}>
                <Flex
                  onSubmit={onTransferClick}
                  sx={{ width: '100%', flexDirection: 'column' }}
                  as="form"
                >
                  <Input
                    onChange={e => setAddress(e.currentTarget.value)}
                    placeholder="ETH Address 0x0..."
                  />
                </Flex>
                <Flex mt={2}>
                  <Button sx={{ bg: 'green' }} onClick={onTransferClick} variant="quartiary">
                    Confirm
                  </Button>
                  <Button
                    sx={{ bg: 'red' }}
                    ml={2}
                    onClick={() => setTransfer(false)}
                    variant="quartiary"
                  >
                    Cancel
                  </Button>
                </Flex>
              </Box>
            )}
            {onSaleActive && (
              <Box sx={{ width: '100%' }}>
                <Flex
                  onSubmit={onTransferClick}
                  sx={{ width: '100%', flexDirection: 'column' }}
                  as="form"
                >
                  <Input
                    onChange={e => setPrice(e.currentTarget.value)}
                    placeholder="Token Price in ETH"
                  />
                </Flex>
                <Flex mt={2}>
                  <Button sx={{ bg: 'green' }} onClick={onSaleClick} variant="quartiary">
                    Confirm
                  </Button>
                  <Button
                    sx={{ bg: 'red' }}
                    ml={2}
                    onClick={() => setOnSale(false)}
                    variant="quartiary"
                  >
                    Cancel
                  </Button>
                </Flex>
              </Box>
            )}
            {!transfer && !onSaleActive && (
              <Flex sx={{ flexDirection: 'column', width: '100%', justifyContent: 'center' }}>
                <Button onClick={() => setTransfer(!transfer)} variant="tertiary">
                  Transfer
                </Button>
                {isOnSale ? (
                  <Button
                    mt={2}
                    onClick={() => onSale && setTokenSale(token.id, token.price, false)}
                    variant="tertiary"
                  >
                    Remove from Sale
                  </Button>
                ) : (
                  <Button mt={2} onClick={() => setOnSale(!onSaleActive)} variant="tertiary">
                    Put Token for Sale
                  </Button>
                )}
              </Flex>
            )}
          </Flex>
        )}
        {onBuy && (
          <Flex mt={3} sx={{ justifyContent: 'center', width: '100%' }}>
            <Button
              sx={{
                opacity: !!user?.ownedTokens.find(
                  a => utils.formatUnits(a.id) === utils.formatUnits(token.id)
                )
                  ? 0.5
                  : 1,
                pointerEvents: !!user?.ownedTokens.find(
                  a => utils.formatUnits(a.id) === utils.formatUnits(token.id)
                )
                  ? 'none'
                  : 'visible',
              }}
              onClick={onBuyClick}
              variant="quartiary"
            >
              Buy Token
            </Button>
          </Flex>
        )}
      </Box>
    </Card>
  )
}
Example #20
Source File: index.tsx    From slice-machine with Apache License 2.0 4 votes vote down vote up
SliceBuilder: React.FunctionComponent = () => {
  const { Model, store, variation } = useContext(SliceContext);

  const { openLoginModal, checkSimulatorSetup, openToaster } =
    useSliceMachineActions();
  const { simulatorUrl, isWaitingForIframeCheck } = useSelector(
    (state: SliceMachineStoreType) => ({
      simulatorUrl: selectSimulatorUrl(state),
      isWaitingForIframeCheck: selectIsWaitingForIFrameCheck(state),
    })
  );

  if (!store || !Model || !variation) return null;

  // We need to move this state to somewhere global to update the UI if any action from anywhere save or update to the filesystem I'd guess
  const [data, setData] = useState<SliceBuilderState>(initialState);

  const onPush = (data: SliceBuilderState) => {
    setData(data);
    if (data.error && data.status === 403) {
      openLoginModal();
    }
  };

  useEffect(() => {
    if (Model.isTouched) {
      setData(initialState);
    }
  }, [Model.isTouched]);

  // activate/deactivate Success message
  useEffect(() => {
    if (data.done) {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-expect-error
      handleRemoteResponse(openToaster)(data);
    }
  }, [data]);

  useEffect(() => {
    if (!store) return;

    return () => store.reset();
  }, []);

  const sliceView = useMemo(
    () => [{ sliceID: Model.model.id, variationID: variation.id }],
    [Model.model.id, variation.id]
  );

  const onTakingCustomScreenshot = () => {
    checkSimulatorSetup(true, () =>
      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
      store
        .variation(variation.id)
        .generateScreenShot(Model.from, Model.model.name, setData)
    );
  };

  return (
    <Box sx={{ flex: 1 }}>
      <Header
        Model={Model}
        store={store}
        variation={variation}
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-expect-error
        onPush={() => void store.push(Model, onPush)}
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-expect-error
        onSave={() => void store.save(Model, setData)}
        isLoading={data.loading}
        imageLoading={data.imageLoading}
      />
      <FlexEditor
        sx={{ py: 4 }}
        SideBar={
          <SideBar
            Model={Model}
            variation={variation}
            onScreenshot={onTakingCustomScreenshot}
            onHandleFile={(file) =>
              // eslint-disable-next-line @typescript-eslint/no-unsafe-return
              store
                .variation(variation.id)
                .generateCustomScreenShot(
                  Model.from,
                  Model.model.name,
                  setData,
                  file
                )
            }
            imageLoading={data.imageLoading}
          />
        }
      >
        <FieldZones Model={Model} store={store} variation={variation} />
      </FlexEditor>
      <SetupDrawer />
      {isWaitingForIframeCheck && (
        <IframeRenderer
          dryRun
          size={Size.FULL}
          simulatorUrl={simulatorUrl}
          sliceView={sliceView}
        />
      )}
    </Box>
  );
}
Example #21
Source File: ChatWindow.tsx    From chat-window with MIT License 4 votes vote down vote up
render() {
    const {
      title = 'Welcome!',
      subtitle = 'How can we help you?',
      newMessagePlaceholder = 'Start typing...',
      emailInputPlaceholder = 'Enter your email',
      agentAvailableText = "We're online right now!",
      agentUnavailableText = "We're away at the moment.",
      newMessagesNotificationText = 'View new messages',
      companyName,
      isMobile = false,
      isCloseable = true,
      showAgentAvailability = false,
      accountId,
      baseUrl,
    } = this.props;
    const {
      customerId,
      messages = [],
      availableAgents = [],
      isSending,
      isOpen,
      isTransitioning,
      isGameMode,
      shouldDisplayNotifications,
      shouldDisplayBranding,
    } = this.state;

    if (isGameMode) {
      return (
        <EmbeddedGame
          isMobile={isMobile}
          onLoadGame={this.handleGameLoaded}
          onLeaveGame={this.handleLeaveGameMode}
        />
      );
    }

    if (isTransitioning) {
      return null; // TODO: need to figure out the best way to handle this
    }

    if (!isOpen && shouldDisplayNotifications) {
      return (
        <UnreadMessages
          messages={this.getUnreadMessages()}
          newMessagesNotificationText={newMessagesNotificationText}
          isMobile={isMobile}
          onOpen={this.emitOpenWindow}
        />
      );
    }

    // FIXME: only return null for versions of the chat-widget after v1.1.0
    if (!isOpen && !this.isOnDeprecatedVersion()) {
      return null;
    }

    const shouldAskForEmail = this.askForEmailUpfront();
    const hasAvailableAgents = availableAgents.length > 0;
    const replies = this.getQuickReplies(messages);

    return (
      <Flex
        className={isMobile ? 'Mobile' : ''}
        sx={{
          bg: 'background',
          flexDirection: 'column',
          height: '100%',
          width: '100%',
          flex: 1,
        }}
      >
        <Box sx={{bg: 'primary', position: 'relative'}}>
          <Box pt={3} pb={showAgentAvailability ? 12 : 16} px={20}>
            {/* TODO: wrap in a button element */}
            {isCloseable && !this.isOnDeprecatedVersion() && (
              <CloseIcon
                className="CloseIcon"
                width={24}
                height={24}
                onClick={this.emitCloseWindow}
              />
            )}
            <Heading
              as="h2"
              className="Papercups-heading"
              sx={{color: 'background', my: 1, mr: 12}}
            >
              {title}
            </Heading>
            <Text sx={{color: 'offset'}}>{subtitle}</Text>
          </Box>

          {showAgentAvailability && (
            <AgentAvailability
              hasAvailableAgents={hasAvailableAgents}
              agentAvailableText={agentAvailableText}
              agentUnavailableText={agentUnavailableText}
            />
          )}
        </Box>

        <Box
          p={3}
          sx={{
            flex: 1,
            boxShadow: 'rgba(0, 0, 0, 0.2) 0px 21px 4px -20px inset',
            overflowY: 'scroll',
          }}
        >
          {messages.map((msg, key) => {
            // Slight hack
            const next = messages[key + 1];
            const isLastInGroup = next
              ? msg.customer_id !== next.customer_id
              : true;
            const shouldDisplayTimestamp = key === messages.length - 1;
            // NB: `msg.type` doesn't come from the server, it's just a way to
            // help identify unsent messages in the frontend for now
            const isMe = isCustomerMessage(msg, customerId);

            return (
              <motion.div
                key={key}
                initial={{opacity: 0, x: isMe ? 2 : -2}}
                animate={{opacity: 1, x: 0}}
                transition={{duration: 0.2, ease: 'easeIn'}}
              >
                <ChatMessage
                  key={key}
                  message={msg}
                  isMe={isMe}
                  companyName={companyName}
                  isLastInGroup={isLastInGroup}
                  shouldDisplayTimestamp={shouldDisplayTimestamp}
                />
              </motion.div>
            );
          })}

          {replies && replies.length > 0 ? (
            <QuickReplies
              replies={replies}
              onSelect={this.handleSelectQuickReply}
            />
          ) : null}

          <div ref={(el) => (this.scrollToEl = el)} />
        </Box>

        {shouldDisplayBranding && <PapercupsBranding />}

        <Box
          px={2}
          sx={{
            borderTop: '1px solid rgb(230, 230, 230)',
            // TODO: only show shadow on focus TextArea below
            boxShadow: 'rgba(0, 0, 0, 0.1) 0px 0px 100px 0px',
          }}
        >
          {/*
            NB: we use a `key` prop here to force re-render on open so
            that the input will auto-focus appropriately
          */}
          <ChatFooter
            key={isOpen ? 1 : 0}
            accountId={accountId}
            baseUrl={baseUrl}
            placeholder={newMessagePlaceholder}
            emailInputPlaceholder={emailInputPlaceholder}
            isSending={isSending}
            shouldRequireEmail={shouldAskForEmail}
            onSendMessage={this.handleSendMessage}
          />
        </Box>

        <img
          alt="Papercups"
          src="https://papercups.s3.us-east-2.amazonaws.com/papercups-logo.svg"
          width="0"
          height="0"
        />
      </Flex>
    );
  }
Example #22
Source File: slices.tsx    From slice-machine with Apache License 2.0 4 votes vote down vote up
SlicesIndex: React.FunctionComponent = () => {
  const libraries = useContext(LibrariesContext);
  const { openCreateSliceModal, closeCreateSliceModal, createSlice } =
    useSliceMachineActions();

  const { isCreateSliceModalOpen, isCreatingSlice, localLibs, remoteLibs } =
    useSelector((store: SliceMachineStoreType) => ({
      isCreateSliceModalOpen: isModalOpen(store, ModalKeysEnum.CREATE_SLICE),
      isCreatingSlice: isLoading(store, LoadingKeysEnum.CREATE_SLICE),
      localLibs: getLibraries(store),
      remoteLibs: getRemoteSlices(store),
    }));

  const _onCreate = ({
    sliceName,
    from,
  }: {
    sliceName: string;
    from: string;
  }) => {
    createSlice(sliceName, from);
  };

  const localLibraries: LibraryState[] | undefined = libraries?.filter(
    (l) => l.isLocal
  );

  const sliceCount = (libraries || []).reduce((count, lib) => {
    if (!lib) {
      return count;
    }

    return count + lib.components.length;
  }, 0);

  return (
    <>
      <Container
        sx={{
          display: "flex",
          flex: 1,
        }}
      >
        <Box
          as={"main"}
          sx={{
            flex: 1,
            display: "flex",
            flexDirection: "column",
          }}
        >
          <Header
            ActionButton={
              localLibraries?.length != 0 && sliceCount != 0 ? (
                <CreateSliceButton
                  onClick={openCreateSliceModal}
                  loading={isCreatingSlice}
                />
              ) : undefined
            }
            MainBreadcrumb={
              <>
                <MdHorizontalSplit /> <Text ml={2}>Slices</Text>
              </>
            }
            breadrumbHref="/slices"
          />
          {libraries && (
            <Flex
              sx={{
                flex: 1,
                flexDirection: "column",
              }}
            >
              {sliceCount === 0 ? (
                <Flex
                  sx={{
                    flex: 1,
                    justifyContent: "center",
                    alignItems: "center",
                  }}
                >
                  <EmptyState
                    title={"What are Slices?"}
                    onCreateNew={openCreateSliceModal}
                    isLoading={isCreatingSlice}
                    buttonText={"Create one"}
                    documentationComponent={
                      <>
                        Slices are sections of your website. Prismic documents
                        contain a dynamic "Slice Zone" that allows content
                        creators to add, edit, and rearrange Slices to compose
                        dynamic layouts for any page design.{" "}
                        <Link
                          target={"_blank"}
                          href={"https://prismic.io/docs/core-concepts/slices"}
                          sx={(theme) => ({ color: theme?.colors?.primary })}
                        >
                          Learn more
                        </Link>
                        .
                      </>
                    }
                  />
                </Flex>
              ) : (
                libraries.map((lib: LibraryState) => {
                  const { name, isLocal, components } = lib;
                  return (
                    <Flex
                      key={name}
                      sx={{
                        flexDirection: "column",
                        "&:not(last-of-type)": {
                          mb: 4,
                        },
                      }}
                    >
                      <Flex
                        sx={{
                          alignItems: "center",
                          justifyContent: "space-between",
                        }}
                      >
                        <Flex
                          sx={{
                            alignItems: "center",
                            fontSize: 3,
                            lineHeight: "48px",
                            fontWeight: "heading",
                            mb: 1,
                          }}
                        >
                          <Text>{name}</Text>
                        </Flex>
                        {!isLocal && <p>⚠️ External libraries are read-only</p>}
                      </Flex>
                      <Grid
                        elems={components.map(([e]) => e)}
                        defineElementKey={(slice: SliceState) =>
                          slice.model.name
                        }
                        renderElem={(slice: SliceState) => {
                          return SharedSlice.render({
                            displayStatus: true,
                            slice,
                          });
                        }}
                      />
                    </Flex>
                  );
                })
              )}
            </Flex>
          )}
        </Box>
      </Container>
      {localLibraries && localLibraries.length > 0 && (
        <CreateSliceModal
          isCreatingSlice={isCreatingSlice}
          isOpen={isCreateSliceModalOpen}
          close={closeCreateSliceModal}
          libraries={localLibs}
          remoteSlices={remoteLibs}
          onSubmit={({ sliceName, from }) => _onCreate({ sliceName, from })}
        />
      )}
    </>
  );
}
Example #23
Source File: EmailForm.tsx    From chat-window with MIT License 4 votes vote down vote up
EmailForm = ({
  newMessagePlaceholder,
  emailInputPlaceholder,
  isSending,
  onSendMessage,
}: Props) => {
  const [message, setMessage] = React.useState('');
  const [email, setEmail] = React.useState('');

  const hasValidEmail = email && email.length > 5 && email.indexOf('@') !== -1;
  const hasValidMessage = message && message.trim().length > 0;
  const isDisabled = !!isSending || !hasValidEmail || !hasValidMessage;

  const handleMessageChange = (e: React.ChangeEvent<HTMLTextAreaElement>) =>
    setMessage(e.target.value);

  const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) =>
    setEmail(e.target.value);

  const handleSendMessage = (e?: any) => {
    e && e.preventDefault();

    onSendMessage({body: message}, email);
    setMessage('');
    setEmail('');
  };

  const handleKeyDown = (e: any) => {
    const {key, shiftKey} = e;

    if (!shiftKey && key === 'Enter') {
      handleSendMessage(e);
    }
  };

  return (
    <Flex
      py={2}
      px={3}
      sx={{
        flex: 1,
        flexDirection: 'column',
        boxShadow: 'rgba(0, 0, 0, 0.2) 0px 21px 4px -20px inset',
        overflowY: 'scroll',
      }}
    >
      <Box py={1} sx={{borderBottom: '1px solid rgb(230, 230, 230)'}}>
        <Input
          sx={{variant: 'styles.input.transparent'}}
          placeholder={emailInputPlaceholder || '[email protected]'}
          autoFocus
          value={email}
          onChange={handleEmailChange}
        />
      </Box>
      <Box py={2} sx={{flex: 1}}>
        <ResizableTextArea
          sx={{
            fontFamily: 'body',
            color: 'input',
            variant: 'styles.input.transparent',
          }}
          className="TextArea--transparent"
          placeholder={newMessagePlaceholder || 'Write your message...'}
          value={message}
          onKeyDown={handleKeyDown}
          onChange={handleMessageChange}
        />
      </Box>
      <Flex sx={{justifyContent: 'flex-end'}}>
        <Button
          variant="link"
          type="submit"
          sx={{
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            borderRadius: '50%',
            height: '36px',
            width: '36px',
            padding: 0,
          }}
          onClick={handleSendMessage}
        >
          <SendIcon
            width={16}
            height={16}
            fill={isDisabled ? 'muted' : 'primary'}
          />
        </Button>
      </Flex>
    </Flex>
  );
}
Example #24
Source File: ModalMapSelect.tsx    From HappyIslandDesigner with MIT License 4 votes vote down vote up
function IslandLayoutSelector() {
  const [layoutType, setLayoutType] = useState<LayoutType>(LayoutType.none);
  const [layout, setLayout] = useState<number>(-1);
  const [help, setHelp] = useState<boolean>(false);

  useEffect(() => {
    if (layout != -1)
    {
      const layoutData = getLayouts(layoutType)[layout];
      loadMapFromJSONString(layoutData.data);
      CloseMapSelectModal();
    }
  }, [layoutType, layout]);

  function getLayouts(type: LayoutType) {
    switch (type) {
      case LayoutType.west:
        return Layouts.west;
      case LayoutType.south:
        return Layouts.south;
      case LayoutType.east:
        return Layouts.east;
      case LayoutType.blank:
        return Layouts.blank;
    }
    return [];
  }

  if (help) {
    return (
      <Flex p={[0, 3]} sx={{flexDirection: 'column', alignItems: 'center', position: 'relative'}}>
        <Box sx={{position: 'absolute', left: 0, top: [1, 30]}}>
          <Button variant='icon' onClick={() => setHelp(false)}>
            <Image sx={{width: 'auto'}} src='static/img/back.png' />
          </Button>
        </Box>
        <Image sx={{width: 100, margin: 'auto'}} src={'static/img/blathers.png'}/>
        <Heading m={3} sx={{px: layoutType ? 4 : 0, textAlign: 'center'}}>{'Please help contribute!'}</Heading>
        <Text my={2}>{'Sorry, we don\'t have all the map templates yet (there are almost 100 river layouts in the game!). Each option you see here has been hand-made by a member of the community.'}</Text>
        <Text my={2}>{'You can use the \'Upload Screenshot\' tool to trace an image of your island. When you\'re done please consider contributing your island map in either the '}<Link href={'https://github.com/eugeneration/HappyIslandDesigner/issues/59'}>Github</Link>{' or '}<Link href={'https://discord.gg/EtaqD5H'}>Discord</Link>!</Text>
        <Text my={2}>{'Please note that your island may have different shaped rock formations, beaches, and building positions than another island with the same river layout.'}</Text>
      </Flex>
    )
  }

  let content;
  if (layoutType != LayoutType.none) {
    var layouts: Array<Layout> = getLayouts(layoutType);
    content = (
      <Grid
        gap={0}
        columns={[2, 3, 4]}
        sx={{justifyItems: 'center' }}>
        {
          layouts.map((layout, index) => (
            <Card
              key={index}
              onClick={() => {
                confirmDestructiveAction(
                  'Clear your map? You will lose all unsaved changes.',
                  () => {
                    setLayout(index);
                  });
              }}>
              <Image variant='card' src={`static/img/layouts/${layoutType}-${layout.name}.png`}/>
            </Card>
          )).concat(
            <Card key={'help'} onClick={()=>{setHelp(true)}}>
              <Image sx={{width: 24}} src={'static/img/menu-help.png'} />
              <Text sx={{fontFamily: 'body'}}>{'Why isn\'t my map here?'}</Text>
            </Card>
          )
        }
      </Grid>
    );
  }
  else {
    content = (
      <Flex sx={{flexDirection: ['column', 'row'], alignItems: 'center'}}>
        <Card onClick={() => setLayoutType(LayoutType.west)}><Image variant='card' src={'static/img/island-type-west.png'}/></Card>
        <Card onClick={() => setLayoutType(LayoutType.south)}><Image variant='card' src={'static/img/island-type-south.png'}/></Card>
        <Card onClick={() => setLayoutType(LayoutType.east)}><Image variant='card' src={'static/img/island-type-east.png'}/></Card>
        <Card onClick={() => {
          setLayoutType(LayoutType.blank);
          confirmDestructiveAction(
            'Clear your map? You will lose all unsaved changes.',
            () => {
              setLayout(0);
            });
        }}><Image variant='card' src={'static/img/island-type-blank.png'}/></Card>      </Flex>
    );
  }
  return (
    <Box p={[0, 3]} sx={{position: 'relative'}}>
      {layoutType && <Box sx={{position: 'absolute', top: [1, 3]}}>
        <Button variant='icon' onClick={() => setLayoutType(LayoutType.none)}>
          <Image src='static/img/back.png' />
        </Button>
      </Box>}
      <Heading m={2} sx={{px: layoutType ? 4 : 0, textAlign: 'center'}}>{layoutType ? 'Choose your Island!' : 'Choose your Layout!'}</Heading>
      {layoutType && <Text m={2} sx={{textAlign: 'center'}}>{'You probably won\'t find an exact match, but pick one that roughly resembles your island.'}</Text>}
      {content}
    </Box>
  );
}
Example #25
Source File: CreateCustomTypeModal.tsx    From slice-machine with Apache License 2.0 4 votes vote down vote up
CreateCustomTypeModal: React.FC = () => {
  const { createCustomType, closeCreateCustomTypeModal } =
    useSliceMachineActions();

  const {
    customTypeIds,
    isCreateCustomTypeModalOpen,
    isCreatingCustomType,
    customTypeLabels,
  } = useSelector((store: SliceMachineStoreType) => ({
    customTypeIds: selectAllCustomTypeIds(store),
    customTypeLabels: selectAllCustomTypeLabels(store),
    isCreateCustomTypeModalOpen: isModalOpen(
      store,
      ModalKeysEnum.CREATE_CUSTOM_TYPE
    ),
    isCreatingCustomType: isLoading(store, LoadingKeysEnum.CREATE_CUSTOM_TYPE),
  }));

  const createCustomTypeAndTrack = ({
    id,
    label,
    repeatable,
  }: {
    id: string;
    label: string;
    repeatable: boolean;
  }) => {
    const name = label || id;

    void Tracker.get().trackCreateCustomType({
      id,
      name,
      repeatable,
    });
    createCustomType(id, name, repeatable);
  };

  return (
    <ModalFormCard
      dataCy="create-ct-modal"
      isOpen={isCreateCustomTypeModalOpen}
      widthInPx="530px"
      formId="create-custom-type"
      buttonLabel={"Create"}
      close={closeCreateCustomTypeModal}
      onSubmit={createCustomTypeAndTrack}
      isLoading={isCreatingCustomType}
      initialValues={{
        repeatable: true,
        id: "",
        label: "",
      }}
      validate={({ id, label }) => {
        const errors: FormikErrors<{
          repeatable: boolean;
          id: string;
          label: string;
        }> = {};

        if (!label || !label.length) {
          errors.label = "Cannot be empty.";
        }

        if (!errors.label && customTypeLabels.includes(label)) {
          errors.label = "Custom Type name is already taken.";
        }

        if (!id || !id.length) {
          errors.id = "ID cannot be empty.";
        }

        if (!errors.id && id && !/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/.exec(id)) {
          errors.id = "Invalid id: No special characters allowed.";
        }
        if (
          !errors.id &&
          id &&
          customTypeIds
            .map((customTypeId) => customTypeId.toLowerCase())
            .includes(id)
        ) {
          errors.id = `ID "${id}" exists already.`;
        }

        return Object.keys(errors).length > 0 ? errors : undefined;
      }}
      content={{
        title: "Create a new custom type",
      }}
    >
      {({ errors }) => (
        <Box>
          <SelectRepeatable />
          <InputBox
            name="label"
            label="Custom Type Name"
            dataCy="ct-name-input"
            placeholder="My Custom Type"
            error={errors.label}
          />
          <InputBox
            name="id"
            dataCy="ct-id-input"
            label="Custom Type ID"
            placeholder="my-custom-type"
            error={errors.id}
          />
        </Box>
      )}
    </ModalFormCard>
  );
}