[FEAT] update the Slector to support add element
This commit is contained in:
parent
6181022814
commit
619e9aa4b8
1
.gitignore
vendored
1
.gitignore
vendored
@ -64,3 +64,4 @@ __pycache__
|
||||
|
||||
.design/
|
||||
.vscode/
|
||||
front/storybook-static
|
||||
|
3
front/pnpm-lock.yaml
generated
3
front/pnpm-lock.yaml
generated
@ -53,9 +53,6 @@ importers:
|
||||
dayjs:
|
||||
specifier: 1.11.13
|
||||
version: 1.11.13
|
||||
framer-motion:
|
||||
specifier: 11.5.4
|
||||
version: 11.5.4(@emotion/is-prop-valid@1.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
history:
|
||||
specifier: 5.3.0
|
||||
version: 5.3.0
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { RefObject } from 'react';
|
||||
|
||||
import {
|
||||
Input,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
|
117
front/src/components/form/FormSelect.stories.tsx
Normal file
117
front/src/components/form/FormSelect.stories.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
import { FormSelect } from '@/components/form/FormSelect';
|
||||
import { useFormidable } from '@/components/form/Formidable';
|
||||
|
||||
export default {
|
||||
title: 'Components/FormSelect',
|
||||
};
|
||||
|
||||
type BasicFormData = {
|
||||
data?: number;
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
return (
|
||||
<FormSelect
|
||||
label="Simple Title"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
keyInputValue="id"
|
||||
options={[{ id: 111 }, { id: 222 }, { id: 333 }, { id: 123 }]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChangeKeys = () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
return (
|
||||
<FormSelect
|
||||
label="Simple Title for (ChangeKeys)"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
keyInputKey="key"
|
||||
keyInputValue="plop"
|
||||
options={[
|
||||
{ key: 111, plop: 'first Item' },
|
||||
{ key: 222, plop: 'Second Item' },
|
||||
{ key: 333, plop: 'third item' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const ChangeName = () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
return (
|
||||
<FormSelect
|
||||
label="Simple Title for (ChangeName)"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
options={[
|
||||
{ id: 111, name: 'first Item' },
|
||||
{ id: 222, name: 'Second Item' },
|
||||
{ id: 333, name: 'third item' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const AddableItem = () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
const [data, setData] = useState([
|
||||
{ id: 111, name: 'first Item' },
|
||||
{ id: 222, name: 'Second Item' },
|
||||
{ id: 333, name: 'third item' },
|
||||
]);
|
||||
return (
|
||||
<FormSelect
|
||||
label="Simple Title for (ChangeName)"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
addNewItem={(data: string) => {
|
||||
let upperId = 0;
|
||||
setData((previous) => {
|
||||
previous.forEach((element) => {
|
||||
if (element['id'] > upperId) {
|
||||
upperId = element['id'];
|
||||
}
|
||||
});
|
||||
upperId++;
|
||||
return [...previous, { id: upperId, name: data }];
|
||||
});
|
||||
return upperId;
|
||||
}}
|
||||
options={data}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const DarkBackground = {
|
||||
render: () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
return (
|
||||
<Box p="4" color="white" bg="gray.800">
|
||||
<FormSelect
|
||||
label="Simple Title for (DarkBackground)"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
options={[
|
||||
{ id: 111, name: 'first Item' },
|
||||
{ id: 222, name: 'Second Item' },
|
||||
{ id: 333, name: 'third item' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'some story **markdown**',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -1,18 +1,32 @@
|
||||
import { RefObject } from 'react';
|
||||
|
||||
import { Text } from '@chakra-ui/react';
|
||||
|
||||
import { FormGroup } from '@/components/form/FormGroup';
|
||||
import { UseFormidableReturn } from '@/components/form/Formidable';
|
||||
import { SelectSingle } from '@/components/select/SelectSingle';
|
||||
|
||||
export type FormSelectProps = {
|
||||
// Generic Form input
|
||||
form: UseFormidableReturn;
|
||||
// Form: Name of the variable
|
||||
variableName: string;
|
||||
// Forward object reference
|
||||
ref?: RefObject<any>;
|
||||
// Form: Label of the input
|
||||
label?: string;
|
||||
// Form: Placeholder if nothing is selected
|
||||
placeholder?: string;
|
||||
// Form: Specify if the element is required or not
|
||||
isRequired?: boolean;
|
||||
options?: object[];
|
||||
// List of object options
|
||||
options: object[];
|
||||
// in the option specify the value Key
|
||||
keyInputKey?: string;
|
||||
// in the option specify the value field
|
||||
keyInputValue?: string;
|
||||
// Add capability to add an item (no key but only value)
|
||||
addNewItem?: (data: string) => number | string;
|
||||
};
|
||||
|
||||
export const FormSelect = ({
|
||||
@ -21,9 +35,18 @@ export const FormSelect = ({
|
||||
ref,
|
||||
placeholder,
|
||||
options,
|
||||
keyInputKey,
|
||||
keyInputValue = 'name',
|
||||
addNewItem,
|
||||
...rest
|
||||
}: FormSelectProps) => {
|
||||
// if set add capability to add the search item
|
||||
const onCreate = !addNewItem
|
||||
? undefined
|
||||
: (data: string) => {
|
||||
const ret = addNewItem(data);
|
||||
form.setValues({ [variableName]: ret });
|
||||
};
|
||||
return (
|
||||
<FormGroup
|
||||
isModify={form.isModify[variableName]}
|
||||
@ -35,7 +58,9 @@ export const FormSelect = ({
|
||||
value={form.values[variableName]}
|
||||
options={options}
|
||||
onChange={(value) => form.setValues({ [variableName]: value })}
|
||||
keyKey={keyInputKey}
|
||||
keyValue={keyInputValue}
|
||||
onCreate={onCreate}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
117
front/src/components/form/FormSelectMultiple.stories.tsx
Normal file
117
front/src/components/form/FormSelectMultiple.stories.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
import { FormSelectMultiple } from '@/components/form/FormSelectMultiple';
|
||||
import { useFormidable } from '@/components/form/Formidable';
|
||||
|
||||
export default {
|
||||
title: 'Components/FormSelectMultipleMultiple',
|
||||
};
|
||||
|
||||
type BasicFormData = {
|
||||
data?: number[];
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
return (
|
||||
<FormSelectMultiple
|
||||
label="Simple Title"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
keyInputValue="id"
|
||||
options={[{ id: 111 }, { id: 222 }, { id: 333 }, { id: 123 }]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChangeKeys = () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
return (
|
||||
<FormSelectMultiple
|
||||
label="Simple Title for (ChangeKeys)"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
keyInputKey="key"
|
||||
keyInputValue="plop"
|
||||
options={[
|
||||
{ key: 111, plop: 'first Item' },
|
||||
{ key: 222, plop: 'Second Item' },
|
||||
{ key: 333, plop: 'third item' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const ChangeName = () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
return (
|
||||
<FormSelectMultiple
|
||||
label="Simple Title for (ChangeName)"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
options={[
|
||||
{ id: 111, name: 'first Item' },
|
||||
{ id: 222, name: 'Second Item' },
|
||||
{ id: 333, name: 'third item' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const AddableItem = () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
const [data, setData] = useState([
|
||||
{ id: 111, name: 'first Item' },
|
||||
{ id: 222, name: 'Second Item' },
|
||||
{ id: 333, name: 'third item' },
|
||||
]);
|
||||
return (
|
||||
<FormSelectMultiple
|
||||
label="Simple Title for (ChangeName)"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
addNewItem={(data: string) => {
|
||||
let upperId = 0;
|
||||
setData((previous) => {
|
||||
previous.forEach((element) => {
|
||||
if (element['id'] > upperId) {
|
||||
upperId = element['id'];
|
||||
}
|
||||
});
|
||||
upperId++;
|
||||
return [...previous, { id: upperId, name: data }];
|
||||
});
|
||||
return upperId;
|
||||
}}
|
||||
options={data}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const DarkBackground = {
|
||||
render: () => {
|
||||
const form = useFormidable<BasicFormData>({});
|
||||
return (
|
||||
<Box p="4" color="white" bg="gray.800">
|
||||
<FormSelectMultiple
|
||||
label="Simple Title for (DarkBackground)"
|
||||
form={form}
|
||||
variableName={'data'}
|
||||
options={[
|
||||
{ id: 111, name: 'first Item' },
|
||||
{ id: 222, name: 'Second Item' },
|
||||
{ id: 333, name: 'third item' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'some story **markdown**',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -5,14 +5,26 @@ import { UseFormidableReturn } from '@/components/form/Formidable';
|
||||
import { SelectMultiple } from '@/components/select/SelectMultiple';
|
||||
|
||||
export type FormSelectMultipleProps = {
|
||||
// Generic Form input
|
||||
form: UseFormidableReturn;
|
||||
// Form: Name of the variable
|
||||
variableName: string;
|
||||
// Forward object reference
|
||||
ref?: RefObject<any>;
|
||||
// Form: Label of the input
|
||||
label?: string;
|
||||
// Form: Placeholder if nothing is selected
|
||||
placeholder?: string;
|
||||
// Form: Specify if the element is required or not
|
||||
isRequired?: boolean;
|
||||
options?: object[];
|
||||
// List of object options
|
||||
options: object[];
|
||||
// in the option specify the value Key
|
||||
keyInputKey?: string;
|
||||
// in the option specify the value field
|
||||
keyInputValue?: string;
|
||||
// Add capability to add an item (no key but only value)
|
||||
addNewItem?: (data: string) => number | string;
|
||||
};
|
||||
|
||||
export const FormSelectMultiple = ({
|
||||
@ -21,9 +33,20 @@ export const FormSelectMultiple = ({
|
||||
ref,
|
||||
placeholder,
|
||||
options,
|
||||
keyInputKey,
|
||||
keyInputValue = 'name',
|
||||
addNewItem,
|
||||
...rest
|
||||
}: FormSelectMultipleProps) => {
|
||||
// if set add capability to add the search item
|
||||
const onCreate = !addNewItem
|
||||
? undefined
|
||||
: (data: string) => {
|
||||
const ret = addNewItem(data);
|
||||
form.setValues({
|
||||
[variableName]: [...(form.values[variableName] ?? []), ret],
|
||||
});
|
||||
};
|
||||
return (
|
||||
<FormGroup
|
||||
isModify={form.isModify[variableName]}
|
||||
@ -35,7 +58,9 @@ export const FormSelectMultiple = ({
|
||||
values={form.values[variableName]}
|
||||
options={options}
|
||||
onChange={(value) => form.setValues({ [variableName]: value })}
|
||||
keyKey={keyInputKey}
|
||||
keyValue={keyInputValue}
|
||||
onCreate={onCreate}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { Box, Button, Flex, Text } from '@chakra-ui/react';
|
||||
import { MdAdd } from 'react-icons/md';
|
||||
|
||||
import { isNullOrUndefined } from '@/utils/validator';
|
||||
import { isNullOrUndefined, isNumber } from '@/utils/validator';
|
||||
|
||||
export type SelectListModel = {
|
||||
id: any;
|
||||
@ -21,7 +22,11 @@ const optionToOptionDisplay = (
|
||||
const out: SelectListModel[] = [];
|
||||
data.forEach((element) => {
|
||||
if (search) {
|
||||
if (!element.name.toLowerCase().includes(search.toLowerCase())) {
|
||||
if (isNumber(element.name)) {
|
||||
if (!element.name.toString().includes(search.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
} else if (!element.name.toLowerCase().includes(search.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -39,12 +44,15 @@ export type SelectListProps = {
|
||||
selected: SelectListModel[];
|
||||
onSelectValue: (data: SelectListModel) => void;
|
||||
search?: string;
|
||||
// if set add capability to add the search item
|
||||
onCreate?: (data: string) => void;
|
||||
};
|
||||
export const SelectList = ({
|
||||
options,
|
||||
selected,
|
||||
onSelectValue,
|
||||
search,
|
||||
onCreate,
|
||||
}: SelectListProps) => {
|
||||
const displayedValue = optionToOptionDisplay(options, selected, search);
|
||||
const scrollToRef = useRef<HTMLButtonElement | null>(null);
|
||||
@ -92,6 +100,20 @@ export const SelectList = ({
|
||||
</Text>
|
||||
</Button>
|
||||
))}
|
||||
{onCreate && search && search.length > 0 && (
|
||||
<Button
|
||||
marginY="1px"
|
||||
borderRadius="0px"
|
||||
autoFocus={false}
|
||||
_hover={{ backgroundColor: 'gray.400' }}
|
||||
onClick={() => onCreate(search)}
|
||||
>
|
||||
<Flex marginRight="auto">
|
||||
<MdAdd />
|
||||
<Text autoFocus={false}>Create '{search}'</Text>
|
||||
</Flex>
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
|
@ -23,6 +23,8 @@ export type SelectMultipleProps = {
|
||||
keyKey?: string;
|
||||
keyValue?: string;
|
||||
ref?: RefObject<any>;
|
||||
// if set add capability to add the search item
|
||||
onCreate?: (data: string) => void;
|
||||
};
|
||||
|
||||
export const SelectMultiple = ({
|
||||
@ -32,6 +34,7 @@ export const SelectMultiple = ({
|
||||
ref,
|
||||
keyKey = 'id',
|
||||
keyValue = keyKey,
|
||||
onCreate,
|
||||
}: SelectMultipleProps) => {
|
||||
const [showList, setShowList] = useState(false);
|
||||
const transformedOption = useMemo(() => {
|
||||
@ -85,6 +88,12 @@ export const SelectMultiple = ({
|
||||
return;
|
||||
}
|
||||
};
|
||||
const createNewItem = !onCreate
|
||||
? undefined
|
||||
: (data: string) => {
|
||||
onCreate(data);
|
||||
setCurrentSearch(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" width="full" gap="0px">
|
||||
@ -137,6 +146,7 @@ export const SelectMultiple = ({
|
||||
selected={selectedOptions}
|
||||
search={currentSearch}
|
||||
onSelectValue={selectValue}
|
||||
onCreate={createNewItem}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
@ -17,6 +17,8 @@ export type SelectSingleProps = {
|
||||
keyKey?: string;
|
||||
keyValue?: string;
|
||||
ref?: RefObject<any>;
|
||||
// if set add capability to add the search item
|
||||
onCreate?: (data: string) => void;
|
||||
};
|
||||
|
||||
export const SelectSingle = ({
|
||||
@ -26,6 +28,7 @@ export const SelectSingle = ({
|
||||
ref,
|
||||
keyKey = 'id',
|
||||
keyValue = keyKey,
|
||||
onCreate,
|
||||
}: SelectSingleProps) => {
|
||||
const [showList, setShowList] = useState(false);
|
||||
const transformedOption = useMemo(() => {
|
||||
@ -75,6 +78,13 @@ export const SelectSingle = ({
|
||||
}
|
||||
};
|
||||
|
||||
const createNewItem = !onCreate
|
||||
? undefined
|
||||
: (data: string) => {
|
||||
onCreate(data);
|
||||
setCurrentSearch(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" width="full" gap="0px">
|
||||
<Flex>
|
||||
@ -82,7 +92,6 @@ export const SelectSingle = ({
|
||||
ref={refFocus}
|
||||
width="full"
|
||||
onChange={(e) => onChangeInput(e.target.value)}
|
||||
//onSubmit={onSubmit}
|
||||
onFocus={() => setShowList(true)}
|
||||
onBlur={() => setTimeout(() => setShowList(false), 200)}
|
||||
value={
|
||||
@ -114,6 +123,7 @@ export const SelectSingle = ({
|
||||
selected={selectedOptions ? [selectedOptions] : []}
|
||||
search={currentSearch}
|
||||
onSelectValue={selectValue}
|
||||
onCreate={createNewItem}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
Loading…
x
Reference in New Issue
Block a user