📝 Add Contribute docs
This commit is contained in:
@@ -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 && (
|
||||
|
||||
135
apps/builder/src/components/PrimitiveList.tsx
Normal file
135
apps/builder/src/components/PrimitiveList.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user