diff --git a/packages/components/src/form/Button/Button.styles.js b/packages/components/src/form/Button/Button.styles.js index 8b90f958e..67a33b5bc 100644 --- a/packages/components/src/form/Button/Button.styles.js +++ b/packages/components/src/form/Button/Button.styles.js @@ -179,6 +179,10 @@ export const ButtonStyles = createStyles( const buttonTheme = theme.other.button; const iconStyles = { padding: 3, + '& > svg': { + width: 18, + height: 18, + }, }; const getTextAlign = () => { diff --git a/packages/components/src/form/ImagePreviewInput/ImagePreviewInput.js b/packages/components/src/form/ImagePreviewInput/ImagePreviewInput.js index 28e0172c0..6418c0a22 100644 --- a/packages/components/src/form/ImagePreviewInput/ImagePreviewInput.js +++ b/packages/components/src/form/ImagePreviewInput/ImagePreviewInput.js @@ -1,7 +1,8 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { isFunction, isString, isNil } from 'lodash'; -import { CloudUploadIcon, DeleteBinIcon } from '@bubbles-ui/icons/outline/'; +import { CloudUploadIcon } from '@bubbles-ui/icons/outline/'; +import { DeleteBinIcon } from '@bubbles-ui/icons/solid/'; import { Box } from '../../layout/Box'; import { Stack } from '../../layout/Stack'; import { Button } from '../Button'; @@ -98,7 +99,7 @@ const ImagePreviewInput = ({ } }, [imageValue]); - const { classes, cx } = ImagePreviewInputStyles({}); + const { classes } = ImagePreviewInputStyles({}); return ( {!imagePreview ? ( @@ -113,6 +114,7 @@ const ImagePreviewInput = ({ radius={4} style={{ ...previewStyle }} useAria={useAria} + bordered /> {!readonly && !disabled ? ( diff --git a/packages/components/src/form/ListInput/ListInput.js b/packages/components/src/form/ListInput/ListInput.js index 4c1e20586..830a7fe14 100644 --- a/packages/components/src/form/ListInput/ListInput.js +++ b/packages/components/src/form/ListInput/ListInput.js @@ -3,19 +3,17 @@ import { v4 as uuidv4 } from 'uuid'; import PropTypes from 'prop-types'; import { findIndex, isFunction, map } from 'lodash'; import { AddCircleIcon } from '@bubbles-ui/icons/outline'; +import { useId } from '@mantine/hooks'; import { Box } from '../../layout'; import { ListInputStyles } from './ListInput.styles'; import { InputWrapper } from '../InputWrapper'; -import { useId } from '@mantine/hooks'; import { TextInput } from '../TextInput'; import { Button } from '../Button'; import { SortableList } from '../../informative'; -import { ListItem } from './components/ListItem'; +import { ListItem, ItemWrapperWithBorder } from './components/ListItem'; export const LIST_INPUT_DEFAULT_PROPS = { - inputRender: (props) => { - return ; - }, + inputRender: (props) => , listRender: , onChange: () => {}, valueKey: 'value', @@ -23,10 +21,32 @@ export const LIST_INPUT_DEFAULT_PROPS = { errorRequiredMessage: 'Required', hideAddButton: false, useAria: true, + hideInput: false, + withItemBorder: false, + withInputBorder: false, }; export const LIST_INPUT_PROP_TYPES = { hideAddButton: PropTypes.bool, useAria: PropTypes.bool, + valueKey: PropTypes.string, + addButtonLabel: PropTypes.string, + errorRequiredMessage: PropTypes.string, + inputRender: PropTypes.func, + listRender: PropTypes.func, + onChange: PropTypes.func, + required: PropTypes.bool, + readonly: PropTypes.bool, + disabled: PropTypes.bool, + canAdd: PropTypes.bool, + size: PropTypes.any, + value: PropTypes.arrayOf(PropTypes.object), + error: PropTypes.string, + help: PropTypes.string, + description: PropTypes.string, + label: PropTypes.string, + hideInput: PropTypes.bool, + withItemBorder: PropTypes.bool, + withInputBorder: PropTypes.bool, }; const ListInput = ({ @@ -48,6 +68,9 @@ const ListInput = ({ value: originalValue, onChange, useAria, + hideInput, + withItemBorder, + withInputBorder, }) => { const { classes, cx } = ListInputStyles({}); @@ -64,7 +87,7 @@ const ListInput = ({ ...item, __key: uuidv4(), })) - : [] + : [], ); const uuid = useId(); @@ -101,7 +124,7 @@ const ListInput = ({ ...item, __key: uuidv4(), })), - true + true, ); }, [originalValue]); @@ -115,6 +138,8 @@ const ListInput = ({ } }, [value]); + const ListInputWrapper = withInputBorder ? ItemWrapperWithBorder : React.Fragment; + return ( { - return React.cloneElement(ListRender, { - ...props, + itemRender={(itemProps) => + React.cloneElement(ListRender, { + ...itemProps, readonly, + withBorder: withItemBorder, inputRender: InputRender, editingKey, valueKey, errorRequiredMessage, - editItem: () => editItem(props.item), + editItem: () => editItem(itemProps.item), stopEdit: () => setEditingKey(null), onChange: (event) => { - const index = findIndex(value, { __key: props.item.__key }); + const index = findIndex(value, { __key: itemProps.item.__key }); value[index][valueKey] = event; setValue([...value]); setEditingKey(null); }, - }); - }} + }) + } useAria={useAria} /> @@ -158,32 +184,36 @@ const ListInput = ({ ? { display: 'flex', flexDirection: 'row', - gap: theme.spacing[4], + gap: theme.spacing[2], width: '100%', flex: 1, } : {} } > - - {React.cloneElement(InputRender, { - value: valueKey ? activeItem[valueKey] : activeItem, - onChange: (event) => { - setActiveItem(valueKey ? { ...activeItem, [valueKey]: event } : event); - if (event) setHasError(false); - }, - required: true, - error: hasError ? errorRequiredMessage : null, - addItem, - })} - - {!hideAddButton ? ( - - - - ) : null} + {hideInput ? null : ( + + + {React.cloneElement(InputRender, { + value: valueKey ? activeItem[valueKey] : activeItem, + onChange: (event) => { + setActiveItem(valueKey ? { ...activeItem, [valueKey]: event } : event); + if (event) setHasError(false); + }, + required: true, + error: hasError ? errorRequiredMessage : null, + addItem, + })} + + {!hideAddButton ? ( + + + + ) : null} + + )} ) : null} diff --git a/packages/components/src/form/ListInput/ListInput.stories.js b/packages/components/src/form/ListInput/ListInput.stories.js index 422589b0b..4b4506570 100644 --- a/packages/components/src/form/ListInput/ListInput.stories.js +++ b/packages/components/src/form/ListInput/ListInput.stories.js @@ -1,6 +1,8 @@ import React from 'react'; import { LIST_INPUT_DEFAULT_PROPS, ListInput } from './ListInput'; import mdx from './ListInput.mdx'; +import { Box } from '../../layout/Box'; +import { Button } from '../Button'; export default { title: 'Molecules/Form/ListInput', @@ -26,10 +28,16 @@ function ListInputRender({ t, ...props }) { } const Template = ({ children, ...props }) => { + const [showInput, setShowInput] = React.useState(false); return ( - - {children} - + + + {children} + + + ); }; @@ -39,4 +47,5 @@ Playground.args = { // myBooleanProp: false, // mySelectProp: 'Hello' ...LIST_INPUT_DEFAULT_PROPS, + value: [{ value: 'Hola' }, { value: 'Mundo' }], }; diff --git a/packages/components/src/form/ListInput/components/ListItem.js b/packages/components/src/form/ListInput/components/ListItem.js index 69ba32676..f6da0fabf 100644 --- a/packages/components/src/form/ListInput/components/ListItem.js +++ b/packages/components/src/form/ListInput/components/ListItem.js @@ -1,14 +1,40 @@ import React, { forwardRef, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; import { RemoveIcon, SortDragIcon } from '@bubbles-ui/icons/outline'; import { CheckIcon, DeleteBinIcon, EditWriteIcon } from '@bubbles-ui/icons/solid'; +import { isFunction } from 'lodash'; import { Box, Stack } from '../../../layout'; import { ActionButton } from '../../ActionButton'; -import { isFunction } from 'lodash'; -const ItemValueRender2 = ({ item }) => { - return ({ width: '100%' })} dangerouslySetInnerHTML={{ __html: item }} />; +const ItemValueRender2 = ({ item }) => ( + ({ width: '100%' })} dangerouslySetInnerHTML={{ __html: item }} /> +); + +ItemValueRender2.propTypes = { + item: PropTypes.string, }; +export const ItemWrapperWithBorder = ({ children }) => ( + ({ width: '100%', padding: '4px 0' })}> + ({ + border: `1px solid ${theme.other.global.border.color.line.subtle}`, + borderRadius: 4, + padding: theme.spacing[4], + backgroundColor: 'white', + })} + > + {children} + + +); + +ItemWrapperWithBorder.propTypes = { + children: PropTypes.node, +}; + +const getRenderComponent = (Component) => (isFunction(Component) ? : Component); + const ListItem = forwardRef( ( { @@ -27,20 +53,16 @@ const ListItem = forwardRef( readonly, errorRequiredMessage, index, + withBorder, }, - ref + ref, ) => { const [value, setValue] = useState(item[valueKey]); const [hasError, setHasError] = useState(false); - const InputRender = isFunction(IInputRender) ? : IInputRender; - - const ItemContainerRender = isFunction(IItemContainerRender) ? ( - - ) : ( - IItemContainerRender - ); - const ItemValueRender = isFunction(IItemValueRender) ? : IItemValueRender; + const InputRender = getRenderComponent(IInputRender); + const ItemContainerRender = getRenderComponent(IItemContainerRender); + const ItemValueRender = getRenderComponent(IItemValueRender); function update() { if (value) { @@ -55,34 +77,42 @@ const ListItem = forwardRef( setValue(item[valueKey]); }, [item]); - let children = React.cloneElement(ItemContainerRender, { - children: [ - !readonly ? ( - ({ marginRight: theme.spacing[4] })}> - - - ) : null, + const renderSortableIcon = () => ( + ({ marginRight: theme.spacing[4] })}> + + + ); + const renderActionButtons = () => ( + + } + disabled={!!editingKey} + onClick={editItem} + /> + } + disabled={!!editingKey} + onClick={removeItem} + /> + + ); + + const renderChildren = () => { + let children = [ + !readonly ? renderSortableIcon() : null, React.cloneElement(ItemValueRender, { item: item[valueKey], index, key: 2, }), - !readonly ? ( - - } disabled={!!editingKey} onClick={editItem} /> - } disabled={!!editingKey} onClick={removeItem} /> - - ) : null, - ], - }); - if (editingKey === item.__key) { - children = React.cloneElement(ItemContainerRender, { - children: [ - ({ marginRight: theme.spacing[4] })}> - - , - ({ width: '100%', marginRight: theme.spacing[4] })}> + !readonly ? renderActionButtons() : null, + ]; + + if (editingKey === item.__key) { + children = [ + renderSortableIcon(), + ({ width: '100%', marginRight: theme.spacing[4] })} key={2}> {React.cloneElement(InputRender, { value, onChange: (event) => { @@ -94,7 +124,7 @@ const ListItem = forwardRef( error: hasError ? errorRequiredMessage : null, })} , - + } onClick={update} /> } @@ -104,9 +134,13 @@ const ListItem = forwardRef( }} /> , - ], - }); - } + ]; + } + + return React.cloneElement(ItemContainerRender, { children }); + }; + + const Wrapper = withBorder ? ItemWrapperWithBorder : React.Fragment; return ( ({ display: 'flex' })} > - {children} + {renderChildren()} ); - } + }, ); +ListItem.displayName = 'ListItem'; +ListItem.propTypes = { + provided: PropTypes.any, + item: PropTypes.any, + removeItem: PropTypes.any, + editItem: PropTypes.any, + inputRender: PropTypes.any, + itemContainerRender: PropTypes.any, + itemValueRender: PropTypes.any, + editingKey: PropTypes.any, + valueKey: PropTypes.any, + stopEdit: PropTypes.any, + classes: PropTypes.any, + onChange: PropTypes.any, + readonly: PropTypes.any, + errorRequiredMessage: PropTypes.any, + index: PropTypes.any, + withBorder: PropTypes.bool, +}; + export { ListItem }; diff --git a/packages/icons/optimized/outline/sort-drag.svg b/packages/icons/optimized/outline/sort-drag.svg index 07464e6f0..969c73b89 100644 --- a/packages/icons/optimized/outline/sort-drag.svg +++ b/packages/icons/optimized/outline/sort-drag.svg @@ -1,3 +1,3 @@ diff --git a/packages/icons/optimized/solid/delete-bin.svg b/packages/icons/optimized/solid/delete-bin.svg index aa877aedb..010a39217 100644 --- a/packages/icons/optimized/solid/delete-bin.svg +++ b/packages/icons/optimized/solid/delete-bin.svg @@ -1,3 +1,3 @@ -