139
apps/builder/src/components/Toast.tsx
Normal file
139
apps/builder/src/components/Toast.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Flex, HStack, IconButton, Stack, Text } from '@chakra-ui/react'
|
||||
import { AlertIcon, CloseIcon, InfoIcon, SmileIcon } from './icons'
|
||||
import { CodeEditor } from './inputs/CodeEditor'
|
||||
import { LanguageName } from '@uiw/codemirror-extensions-langs'
|
||||
|
||||
export type ToastProps = {
|
||||
title?: string
|
||||
description?: string
|
||||
details?: {
|
||||
content: string
|
||||
lang: LanguageName
|
||||
}
|
||||
status?: 'info' | 'error' | 'success'
|
||||
icon?: React.ReactNode
|
||||
primaryButton?: React.ReactNode
|
||||
secondaryButton?: React.ReactNode
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export const Toast = ({
|
||||
status = 'error',
|
||||
title,
|
||||
description,
|
||||
details,
|
||||
icon,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
onClose,
|
||||
}: ToastProps) => {
|
||||
return (
|
||||
<Flex
|
||||
p={3}
|
||||
rounded="md"
|
||||
bgColor="white"
|
||||
borderWidth="1px"
|
||||
shadow="sm"
|
||||
fontSize="sm"
|
||||
pos="relative"
|
||||
maxW="450px"
|
||||
>
|
||||
<HStack alignItems="flex-start" pr="7" spacing="3" w="full">
|
||||
<Icon customIcon={icon} status={status} />{' '}
|
||||
<Stack spacing={3} flex="1">
|
||||
<Stack spacing={1}>
|
||||
{title && <Text fontWeight="semibold">{title}</Text>}
|
||||
{description && <Text>{description}</Text>}
|
||||
</Stack>
|
||||
|
||||
{details && (
|
||||
<CodeEditor
|
||||
isReadOnly
|
||||
value={details.content}
|
||||
lang={details.lang}
|
||||
maxHeight="200px"
|
||||
maxWidth="calc(450px - 100px)"
|
||||
/>
|
||||
)}
|
||||
{(secondaryButton || primaryButton) && (
|
||||
<HStack>
|
||||
{secondaryButton}
|
||||
{primaryButton}
|
||||
</HStack>
|
||||
)}
|
||||
</Stack>
|
||||
</HStack>
|
||||
|
||||
<IconButton
|
||||
aria-label="Close"
|
||||
icon={<CloseIcon />}
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
variant="ghost"
|
||||
pos="absolute"
|
||||
top={1}
|
||||
right={1}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
const Icon = ({
|
||||
customIcon,
|
||||
status,
|
||||
}: {
|
||||
customIcon?: React.ReactNode
|
||||
status: ToastProps['status']
|
||||
}) => {
|
||||
const color = parseColor(status)
|
||||
const icon = parseIcon(status, customIcon)
|
||||
return (
|
||||
<Flex
|
||||
bgColor={`${color}.50`}
|
||||
boxSize="40px"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
rounded="full"
|
||||
flexShrink={0}
|
||||
>
|
||||
<Flex
|
||||
bgColor={`${color}.100`}
|
||||
boxSize="30px"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
rounded="full"
|
||||
fontSize="18px"
|
||||
color={`${color}.600`}
|
||||
>
|
||||
{icon}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
const parseColor = (status: ToastProps['status']) => {
|
||||
if (!status) return 'red'
|
||||
switch (status) {
|
||||
case 'error':
|
||||
return 'red'
|
||||
case 'success':
|
||||
return 'green'
|
||||
case 'info':
|
||||
return 'blue'
|
||||
}
|
||||
}
|
||||
|
||||
const parseIcon = (
|
||||
status: ToastProps['status'],
|
||||
customIcon?: React.ReactNode
|
||||
) => {
|
||||
if (customIcon) return customIcon
|
||||
switch (status) {
|
||||
case 'error':
|
||||
return <AlertIcon />
|
||||
case 'success':
|
||||
return <SmileIcon />
|
||||
case 'info':
|
||||
return <InfoIcon />
|
||||
}
|
||||
}
|
||||
@@ -604,3 +604,20 @@ export const ShuffleIcon = (props: IconProps) => (
|
||||
<line x1="4" y1="4" x2="9" y2="9"></line>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const InfoIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="16" x2="12" y2="12"></line>
|
||||
<line x1="12" y1="8" x2="12.01" y2="8"></line>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const SmileIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
|
||||
<line x1="9" y1="9" x2="9.01" y2="9"></line>
|
||||
<line x1="15" y1="9" x2="15.01" y2="9"></line>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ type Props = {
|
||||
debounceTimeout?: number
|
||||
withVariableButton?: boolean
|
||||
height?: string
|
||||
maxHeight?: string
|
||||
onChange?: (value: string) => void
|
||||
}
|
||||
export const CodeEditor = ({
|
||||
@@ -32,6 +33,7 @@ export const CodeEditor = ({
|
||||
lang,
|
||||
onChange,
|
||||
height = '250px',
|
||||
maxHeight = '70vh',
|
||||
withVariableButton = true,
|
||||
isReadOnly = false,
|
||||
debounceTimeout = 1000,
|
||||
@@ -93,9 +95,10 @@ export const CodeEditor = ({
|
||||
pos="relative"
|
||||
onMouseEnter={onOpen}
|
||||
onMouseLeave={onClose}
|
||||
maxWidth={props.maxWidth}
|
||||
sx={{
|
||||
'& .cm-editor': {
|
||||
maxH: '70vh',
|
||||
maxH: maxHeight,
|
||||
outline: '0px solid transparent !important',
|
||||
rounded: 'md',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user