2
0

📝 Add Contribute docs

This commit is contained in:
Baptiste Arnaud
2024-01-03 16:29:41 +01:00
parent b3957295bd
commit 65f4fb0d7a
66 changed files with 1453 additions and 519 deletions

View File

@@ -51,7 +51,7 @@ export const DropdownList = <T extends readonly any[]>({
isRequired={isRequired}
as={direction === 'column' ? Stack : HStack}
justifyContent="space-between"
width={label ? 'full' : 'auto'}
width={props.width === 'full' || label ? 'full' : 'auto'}
spacing={direction === 'column' ? 2 : 3}
>
{label && (

View File

@@ -0,0 +1,135 @@
import { Box, Button, Fade, Flex, IconButton, Stack } from '@chakra-ui/react'
import { TrashIcon, PlusIcon } from '@/components/icons'
import React, { useEffect, useState } from 'react'
import { createId } from '@paralleldrive/cuid2'
type ItemWithId<T extends number | string | boolean> = { id: string; value?: T }
export type TableListItemProps<T> = {
item: T
onItemChange: (item: T) => void
}
type Props<T extends number | string | boolean> = {
initialItems?: T[]
addLabel?: string
newItemDefaultProps?: Partial<T>
hasDefaultItem?: boolean
ComponentBetweenItems?: (props: unknown) => JSX.Element
onItemsChange: (items: T[]) => void
children: (props: TableListItemProps<T>) => JSX.Element
}
const addIdToItems = <T extends number | string | boolean>(
items: T[]
): ItemWithId<T>[] => items.map((item) => ({ id: createId(), value: item }))
const removeIdFromItems = <T extends number | string | boolean>(
items: ItemWithId<T>[]
): T[] => items.map((item) => item.value as T)
export const PrimitiveList = <T extends number | string | boolean>({
initialItems,
addLabel = 'Add',
hasDefaultItem,
children,
ComponentBetweenItems,
onItemsChange,
}: Props<T>) => {
const [items, setItems] = useState<ItemWithId<T>[]>()
const [showDeleteIndex, setShowDeleteIndex] = useState<number | null>(null)
useEffect(() => {
if (items) return
if (initialItems) {
setItems(addIdToItems(initialItems))
} else if (hasDefaultItem) {
setItems(addIdToItems([]))
} else {
setItems([])
}
}, [hasDefaultItem, initialItems, items])
const createItem = () => {
if (!items) return
const newItems = [...items, { id: createId() }]
setItems(newItems)
onItemsChange(removeIdFromItems(newItems))
}
const updateItem = (itemIndex: number, newValue: T) => {
if (!items) return
const newItems = items.map((item, idx) =>
idx === itemIndex ? { ...item, value: newValue } : item
)
setItems(newItems)
onItemsChange(removeIdFromItems(newItems))
}
const deleteItem = (itemIndex: number) => () => {
if (!items) return
const newItems = [...items]
newItems.splice(itemIndex, 1)
setItems([...newItems])
onItemsChange(removeIdFromItems([...newItems]))
}
const handleMouseEnter = (itemIndex: number) => () =>
setShowDeleteIndex(itemIndex)
const handleCellChange = (itemIndex: number) => (item: T) =>
updateItem(itemIndex, item)
const handleMouseLeave = () => setShowDeleteIndex(null)
return (
<Stack spacing={0}>
{items?.map((item, itemIndex) => (
<Box key={item.id}>
{itemIndex !== 0 && ComponentBetweenItems && (
<ComponentBetweenItems />
)}
<Flex
pos="relative"
onMouseEnter={handleMouseEnter(itemIndex)}
onMouseLeave={handleMouseLeave}
mt={itemIndex !== 0 && ComponentBetweenItems ? 4 : 0}
justifyContent="center"
pb="4"
>
{children({
item: item.value as T,
onItemChange: handleCellChange(itemIndex),
})}
<Fade
in={showDeleteIndex === itemIndex}
style={{
position: 'absolute',
left: '-15px',
top: '-15px',
zIndex: 1,
}}
unmountOnExit
>
<IconButton
icon={<TrashIcon />}
aria-label="Remove cell"
onClick={deleteItem(itemIndex)}
size="sm"
shadow="md"
/>
</Fade>
</Flex>
</Box>
))}
<Button
leftIcon={<PlusIcon />}
onClick={createItem}
flexShrink={0}
colorScheme="blue"
>
{addLabel}
</Button>
</Stack>
)
}

View File

@@ -109,7 +109,7 @@ export const NumberInput = <HasVariable extends boolean>({
as={direction === 'column' ? Stack : HStack}
isRequired={isRequired}
justifyContent="space-between"
width={label ? 'full' : 'auto'}
width={label || props.width === 'full' ? 'full' : 'auto'}
spacing={direction === 'column' ? 2 : 3}
>
{label && (

View File

@@ -35,6 +35,7 @@ export type TextInputProps = {
placeholder?: string
isDisabled?: boolean
direction?: 'row' | 'column'
width?: 'full'
} & Pick<
InputProps,
| 'autoComplete'
@@ -66,6 +67,7 @@ export const TextInput = forwardRef(function TextInput(
size,
maxWidth,
direction = 'column',
width,
}: TextInputProps,
ref
) {
@@ -141,7 +143,7 @@ export const TextInput = forwardRef(function TextInput(
isRequired={isRequired}
as={direction === 'column' ? Stack : HStack}
justifyContent="space-between"
width={label ? 'full' : 'auto'}
width={label || width === 'full' ? 'full' : 'auto'}
spacing={direction === 'column' ? 2 : 3}
>
{label && (

View File

@@ -28,7 +28,7 @@ type Props = {
helperText?: ReactNode
onChange: (value: string) => void
direction?: 'row' | 'column'
} & Pick<TextareaProps, 'minH'>
} & Pick<TextareaProps, 'minH' | 'width'>
export const Textarea = ({
id,
@@ -43,6 +43,7 @@ export const Textarea = ({
minH,
helperText,
direction = 'column',
width,
}: Props) => {
const inputRef = useRef<HTMLTextAreaElement | null>(null)
const [isTouched, setIsTouched] = useState(false)
@@ -108,7 +109,7 @@ export const Textarea = ({
isRequired={isRequired}
as={direction === 'column' ? Stack : HStack}
justifyContent="space-between"
width={label ? 'full' : 'auto'}
width={label || width === 'full' ? 'full' : 'auto'}
spacing={direction === 'column' ? 2 : 3}
>
{label && (

View File

@@ -46,6 +46,7 @@ type Props = {
helperText?: ReactNode
moreInfoTooltip?: string
direction?: 'row' | 'column'
width?: 'full'
} & Omit<InputProps, 'placeholder'>
export const VariableSearchInput = ({
@@ -58,6 +59,7 @@ export const VariableSearchInput = ({
moreInfoTooltip,
direction = 'column',
isRequired,
width,
...inputProps
}: Props) => {
const focusedItemBgColor = useColorModeValue('gray.200', 'gray.700')
@@ -196,7 +198,7 @@ export const VariableSearchInput = ({
isRequired={isRequired}
as={direction === 'column' ? Stack : HStack}
justifyContent="space-between"
width={label ? 'full' : 'auto'}
width={label || width === 'full' ? 'full' : 'auto'}
spacing={direction === 'column' ? 2 : 3}
>
{label && (

View File

@@ -25,6 +25,7 @@ type Props = {
moreInfoTooltip?: string
direction?: 'row' | 'column'
isRequired?: boolean
width?: 'full'
onChange: (value: string | undefined) => void
}
export const ForgeSelectInput = ({
@@ -38,6 +39,7 @@ export const ForgeSelectInput = ({
moreInfoTooltip,
isRequired,
direction = 'column',
width,
onChange,
}: Props) => {
const { workspace } = useWorkspace()
@@ -83,7 +85,7 @@ export const ForgeSelectInput = ({
isRequired={isRequired}
as={direction === 'column' ? Stack : HStack}
justifyContent="space-between"
width={label ? 'full' : 'auto'}
width={label || width === 'full' ? 'full' : 'auto'}
spacing={direction === 'column' ? 2 : 3}
>
{label && (

View File

@@ -31,10 +31,8 @@ export const ZodDiscriminatedUnionLayout = ({
currentItem={data?.[discriminant]}
onItemSelect={(item) => onDataChange({ ...data, [discriminant]: item })}
items={
[...schema._def.optionsMap.keys()].filter(
(key) =>
isDefined(key) &&
!schema._def.optionsMap.get(key)?._def.layout?.isHidden
[...schema._def.optionsMap.keys()].filter((key) =>
isDefined(key)
) as string[]
}
placeholder={dropdownPlaceholder}

View File

@@ -13,12 +13,15 @@ import {
AccordionIcon,
AccordionItem,
AccordionPanel,
FormLabel,
Stack,
Text,
} from '@chakra-ui/react'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { DropdownList } from '@/components/DropdownList'
import { ForgedBlockDefinition, ForgedBlock } from '@typebot.io/forge-schemas'
import { PrimitiveList } from '@/components/PrimitiveList'
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
const mdComponents = {
a: ({ href, children }) => (
@@ -40,6 +43,8 @@ export const ZodFieldLayout = ({
isInAccordion,
blockDef,
blockOptions,
width,
propName,
onDataChange,
}: {
data: any
@@ -47,6 +52,8 @@ export const ZodFieldLayout = ({
isInAccordion?: boolean
blockDef?: ForgedBlockDefinition
blockOptions?: ForgedBlock['options']
width?: 'full'
propName?: string
onDataChange: (val: any) => void
}) => {
const layout = schema._def.layout as ZodLayoutMetadata<ZodTypeAny> | undefined
@@ -54,7 +61,6 @@ export const ZodFieldLayout = ({
? schema._def.innerType._def.typeName
: schema._def.typeName
if (layout?.isHidden) return null
switch (type) {
case 'ZodObject':
return (
@@ -125,6 +131,7 @@ export const ZodFieldLayout = ({
moreInfoTooltip={layout?.moreInfoTooltip}
placeholder={layout?.placeholder}
direction={layout?.direction}
width={width}
/>
)
}
@@ -143,6 +150,18 @@ export const ZodFieldLayout = ({
isRequired={layout?.isRequired}
moreInfoTooltip={layout?.moreInfoTooltip}
onValueChange={onDataChange}
direction={layout?.direction}
width={width}
/>
)
}
case 'ZodBoolean': {
return (
<SwitchWithLabel
label={layout?.label ?? propName ?? ''}
initialValue={data ?? layout?.defaultValue}
onCheckChange={onDataChange}
moreInfoContent={layout?.moreInfoTooltip}
/>
)
}
@@ -166,10 +185,11 @@ export const ZodFieldLayout = ({
}
moreInfoTooltip={layout?.moreInfoTooltip}
onChange={onDataChange}
width={width}
/>
)
}
if (layout?.input === 'variableDropdown') {
if (layout?.inputType === 'variableDropdown') {
return (
<VariableSearchInput
initialVariableId={data}
@@ -184,10 +204,11 @@ export const ZodFieldLayout = ({
</Markdown>
) : undefined
}
width={width}
/>
)
}
if (layout?.input === 'textarea') {
if (layout?.inputType === 'textarea') {
return (
<Textarea
defaultValue={data ?? layout?.defaultValue}
@@ -204,6 +225,7 @@ export const ZodFieldLayout = ({
withVariableButton={layout?.withVariableButton}
moreInfoTooltip={layout.moreInfoTooltip}
onChange={onDataChange}
width={width}
/>
)
}
@@ -217,11 +239,12 @@ export const ZodFieldLayout = ({
<Markdown components={mdComponents}>{layout.helperText}</Markdown>
) : undefined
}
type={layout?.input === 'password' ? 'password' : undefined}
type={layout?.inputType === 'password' ? 'password' : undefined}
isRequired={layout?.isRequired}
withVariableButton={layout?.withVariableButton}
moreInfoTooltip={layout?.moreInfoTooltip}
onChange={onDataChange}
width={width}
/>
)
}
@@ -240,24 +263,54 @@ const ZodArrayContent = ({
layout: ZodLayoutMetadata<ZodTypeAny> | undefined
isInAccordion?: boolean
onDataChange: (val: any) => void
}) => (
<TableList
onItemsChange={(items) => {
onDataChange(items)
}}
initialItems={data}
addLabel={`Add ${layout?.itemLabel ?? ''}`}
isOrdered={layout?.isOrdered}
>
{({ item, onItemChange }) => (
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
<ZodFieldLayout
schema={schema._def.innerType._def.type}
data={item}
isInAccordion={isInAccordion}
onDataChange={onItemChange}
/>
}) => {
const type = schema._def.innerType
? schema._def.innerType._def.typeName
: schema._def.typeName
if (type === 'ZodString' || type === 'ZodNumber' || type === 'ZodEnum')
return (
<Stack spacing={0}>
{layout?.label && <FormLabel>{layout.label}</FormLabel>}
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
<PrimitiveList
onItemsChange={(items) => {
onDataChange(items)
}}
initialItems={data}
addLabel={`Add ${layout?.itemLabel ?? ''}`}
>
{({ item, onItemChange }) => (
<ZodFieldLayout
schema={schema._def.innerType._def.type}
data={item}
isInAccordion={isInAccordion}
onDataChange={onItemChange}
width="full"
/>
)}
</PrimitiveList>
</Stack>
</Stack>
)}
</TableList>
)
)
return (
<TableList
onItemsChange={(items) => {
onDataChange(items)
}}
initialItems={data}
addLabel={`Add ${layout?.itemLabel ?? ''}`}
isOrdered={layout?.isOrdered}
>
{({ item, onItemChange }) => (
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
<ZodFieldLayout
schema={schema._def.innerType._def.type}
data={item}
isInAccordion={isInAccordion}
onDataChange={onItemChange}
/>
</Stack>
)}
</TableList>
)
}

View File

@@ -98,6 +98,7 @@ export const ZodObjectLayout = ({
data={data?.[key]}
blockDef={blockDef}
blockOptions={blockOptions}
propName={key}
onDataChange={(val) => onDataChange({ ...data, [key]: val })}
/>,
],