Streamfield: allow removing elements
This commit is contained in:
parent
cd1a404351
commit
a6f4f07d9f
|
@ -18,6 +18,8 @@ interface IInput extends Pick<React.InputHTMLAttributes<HTMLInputElement>, 'maxL
|
||||||
defaultValue?: string,
|
defaultValue?: string,
|
||||||
/** Extra class names for the <input> element. */
|
/** Extra class names for the <input> element. */
|
||||||
className?: string,
|
className?: string,
|
||||||
|
/** Extra class names for the outer <div> element. */
|
||||||
|
outerClassName?: string,
|
||||||
/** URL to the svg icon. */
|
/** URL to the svg icon. */
|
||||||
icon?: string,
|
icon?: string,
|
||||||
/** Internal input name. */
|
/** Internal input name. */
|
||||||
|
@ -37,7 +39,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { type = 'text', icon, className, ...filteredProps } = props;
|
const { type = 'text', icon, className, outerClassName, ...filteredProps } = props;
|
||||||
|
|
||||||
const [revealed, setRevealed] = React.useState(false);
|
const [revealed, setRevealed] = React.useState(false);
|
||||||
|
|
||||||
|
@ -48,7 +50,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-1 relative rounded-md shadow-sm'>
|
<div className={classNames('mt-1 relative rounded-md shadow-sm', outerClassName)}>
|
||||||
{icon ? (
|
{icon ? (
|
||||||
<div className='absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none'>
|
<div className='absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none'>
|
||||||
<Icon src={icon} className='h-4 w-4 text-gray-400' aria-hidden='true' />
|
<Icon src={icon} className='h-4 w-4 text-gray-400' aria-hidden='true' />
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Button from '../button/button';
|
import Button from '../button/button';
|
||||||
|
import HStack from '../hstack/hstack';
|
||||||
|
import IconButton from '../icon-button/icon-button';
|
||||||
import Stack from '../stack/stack';
|
import Stack from '../stack/stack';
|
||||||
|
|
||||||
interface IStreamfield {
|
interface IStreamfield {
|
||||||
values: any[],
|
values: any[],
|
||||||
onAddItem?: () => void,
|
onAddItem?: () => void,
|
||||||
|
onRemoveItem?: (i: number) => void,
|
||||||
onChange: (values: any[]) => void,
|
onChange: (values: any[]) => void,
|
||||||
component: React.ComponentType<{ onChange: (value: any) => void, value: any }>,
|
component: React.ComponentType<{ onChange: (value: any) => void, value: any }>,
|
||||||
maxItems?: number,
|
maxItems?: number,
|
||||||
|
@ -15,6 +18,7 @@ interface IStreamfield {
|
||||||
const Streamfield: React.FC<IStreamfield> = ({
|
const Streamfield: React.FC<IStreamfield> = ({
|
||||||
values,
|
values,
|
||||||
onAddItem,
|
onAddItem,
|
||||||
|
onRemoveItem,
|
||||||
onChange,
|
onChange,
|
||||||
component: Component,
|
component: Component,
|
||||||
maxItems = Infinity,
|
maxItems = Infinity,
|
||||||
|
@ -32,11 +36,19 @@ const Streamfield: React.FC<IStreamfield> = ({
|
||||||
<Stack space={4}>
|
<Stack space={4}>
|
||||||
|
|
||||||
<Stack>
|
<Stack>
|
||||||
{values.map((value, i) => {
|
{values.map((value, i) => (
|
||||||
return (
|
<HStack space={2} alignItems='center'>
|
||||||
<Component key={i} onChange={handleChange(i)} value={value} />
|
<Component key={i} onChange={handleChange(i)} value={value} />
|
||||||
);
|
{onRemoveItem && (
|
||||||
})}
|
<IconButton
|
||||||
|
iconClassName='w-4 h-4'
|
||||||
|
className='bg-transparent text-gray-400 hover:text-gray-600'
|
||||||
|
src={require('@tabler/icons/icons/x.svg')}
|
||||||
|
onClick={() => onRemoveItem(i)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{onAddItem && (
|
{onAddItem && (
|
||||||
|
|
|
@ -143,8 +143,8 @@ const ProfileField: React.FC<IProfileField> = ({ value, onChange }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack space={2} grow>
|
<HStack space={2} grow>
|
||||||
<Input type='text' value={value.name} onChange={handleChange('name')} />
|
<Input outerClassName='w-full flex-grow' type='text' value={value.name} onChange={handleChange('name')} />
|
||||||
<Input type='text' value={value.value} onChange={handleChange('value')} />
|
<Input outerClassName='w-full flex-grow' type='text' value={value.value} onChange={handleChange('value')} />
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -254,6 +254,13 @@ const EditProfile: React.FC = () => {
|
||||||
updateData('fields_attributes', fields);
|
updateData('fields_attributes', fields);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRemoveField = (i: number) => {
|
||||||
|
const oldFields = data.fields_attributes || [];
|
||||||
|
const fields = [...oldFields];
|
||||||
|
fields.splice(i, 1);
|
||||||
|
updateData('fields_attributes', fields);
|
||||||
|
};
|
||||||
|
|
||||||
/** Memoized avatar preview URL. */
|
/** Memoized avatar preview URL. */
|
||||||
const avatarUrl = useMemo(() => {
|
const avatarUrl = useMemo(() => {
|
||||||
return data.avatar ? URL.createObjectURL(data.avatar) : account?.avatar;
|
return data.avatar ? URL.createObjectURL(data.avatar) : account?.avatar;
|
||||||
|
@ -419,6 +426,7 @@ const EditProfile: React.FC = () => {
|
||||||
values={data.fields_attributes || []}
|
values={data.fields_attributes || []}
|
||||||
onChange={handleFieldsChange}
|
onChange={handleFieldsChange}
|
||||||
onAddItem={handleAddField}
|
onAddItem={handleAddField}
|
||||||
|
onRemoveItem={handleRemoveField}
|
||||||
component={ProfileField}
|
component={ProfileField}
|
||||||
maxItems={maxFields}
|
maxItems={maxFields}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in New Issue