@chakra-ui/react#InputLeftElement TypeScript Examples
The following examples show how to use
@chakra-ui/react#InputLeftElement.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ChakraDateInput.tsx From ke with MIT License | 6 votes |
ChakraDateInput = forwardRef<HTMLInputElement, ChakraDateInputProps>(
({ className, inputClassName, ...props }, ref) => (
<InputGroup className={className}>
<InputLeftElement
zIndex="unset"
fontSize="20px"
width="44px"
justifyContent="flex-start"
pl="16px"
pointerEvents="none"
>
<Icon as={Calendar} />
</InputLeftElement>
{/* Ðто обёртка */}
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<Input paddingStart="44px" className={inputClassName} {...props} ref={ref} />
</InputGroup>
)
)
Example #2
Source File: ImagesPanel.tsx From react-design-editor with MIT License | 6 votes |
function ImagesPanel() {
return (
<>
<div style={{ padding: '1rem 2rem' }}>
<InputGroup>
<InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
<Input style={{ background: '#fff' }} type="tel" placeholder="Search images" />
</InputGroup>
</div>
</>
)
}
Example #3
Source File: MusicPanel.tsx From react-design-editor with MIT License | 6 votes |
function MusicPanel() {
return (
<>
<div style={{ padding: '1rem 2rem' }}>
<InputGroup>
<InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
<Input style={{ background: '#fff' }} type="tel" placeholder="Search music" />
</InputGroup>
</div>
</>
)
}
Example #4
Source File: TemplatesPanel.tsx From react-design-editor with MIT License | 6 votes |
function TemplatesPanel() {
return (
<>
<div style={{ padding: '1rem 2rem' }}>
<InputGroup>
<InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
<Input style={{ background: '#fff' }} type="tel" placeholder="Search templates" />
</InputGroup>
</div>
</>
)
}
Example #5
Source File: VideosPanel.tsx From react-design-editor with MIT License | 6 votes |
function VideosPanel() {
return (
<>
<div style={{ padding: '1rem 2rem' }}>
<InputGroup>
<InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
<Input style={{ background: '#fff' }} type="tel" placeholder="Search videos" />
</InputGroup>
</div>
</>
)
}
Example #6
Source File: InputLeftElement.tsx From openchakra with MIT License | 6 votes |
InputLeftElementPreview: React.FC<{ component: IComponent }> = ({
component,
}) => {
const { drop, isOver } = useDropComponent(component.id)
const { props, ref } = useInteractive(component, true)
if (isOver) {
props.bg = 'teal.50'
}
return (
<InputLeftElement top="10px" right="10px" {...props} ref={drop(ref)}>
{component.children.map((key: string) => (
<ComponentPreview componentName={key} />
))}
</InputLeftElement>
)
}
Example #7
Source File: ObjectsPanel.tsx From react-design-editor with MIT License | 5 votes |
function ObjectsPanel() {
const [search, setSearch] = useState('')
const [objects, setObjects] = useState<any[]>([])
const [value] = useDebounce(search, 1000)
const { canvas } = useCanvasContext()
useEffect(() => {
getImages('love')
.then((data: any) => setObjects(data))
.catch(console.log)
}, [])
useEffect(() => {
if (value) {
getImages(value)
.then((data: any) => setObjects(data))
.catch(console.log)
}
}, [value])
const renderItems = () => {
return objects.map(obj => {
return (
<div className="object-item-container" onClick={() => downloadImage(obj.uuid)} key={obj.uuid}>
<img className="object-item" src={obj.urls.thumb} />
</div>
)
})
}
const downloadImage = uuid => {
getImage(uuid)
.then(url => {
fabric.loadSVGFromURL(url, (objects, options) => {
const object = fabric.util.groupSVGElements(objects, options)
//@ts-ignore
const workarea = canvas.getObjects().find(obj => obj.id === 'workarea')
canvas.add(object)
object.scaleToHeight(300)
object.center()
object.clipPath = workarea
canvas.renderAll()
})
})
.catch(console.log)
}
return (
<>
<div style={{ padding: '1rem 2rem' }}>
<InputGroup>
<InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
<Input
onChange={e => setSearch(e.target.value)}
style={{ background: '#fff' }}
type="tel"
placeholder="Search objects"
/>
</InputGroup>
</div>
<div style={{ padding: '0 2rem' }} className="objects-list">
{renderItems()}
</div>
</>
)
}
Example #8
Source File: TextPanel.tsx From react-design-editor with MIT License | 5 votes |
function TextPanel() {
const { addObject } = useCoreHandler()
const addHeading = () => {
const options = {
type: 'text',
text: 'Add a heading',
fontSize: 32,
width: 320,
fontWeight: 700,
fontFamily: 'Lexend',
textAlign: 'center',
}
addObject(options)
}
const addSubheading = () => {
const options = {
type: 'text',
text: 'Add a subheading',
fontSize: 24,
width: 320,
fontWeight: 500,
fontFamily: 'Lexend',
textAlign: 'center',
}
addObject(options)
}
const addTextBody = () => {
const options = {
type: 'text',
text: 'Add a little bit of body text',
fontSize: 18,
width: 320,
fontWeight: 300,
fontFamily: 'Lexend',
textAlign: 'center',
}
addObject(options)
}
return (
<>
<div className="panel-text" style={{ padding: '1rem 1.5rem' }}>
<InputGroup>
<InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.600" />} />
<Input style={{ background: '#fff' }} type="tel" placeholder="Search text" />
</InputGroup>
<div className="label">Click text to add to page</div>
<div className="add-text-items">
<div onClick={addHeading} className="add-text-item add-heading">
Add a heading
</div>
<div onClick={addSubheading} className="add-text-item add-subheading">
Add a subheading
</div>
<div onClick={addTextBody} className="add-text-item add-body-text">
Add a litle bit of body text
</div>
</div>
</div>
</>
)
}
Example #9
Source File: ColorPicker.tsx From lucide with ISC License | 5 votes |
function ColorPicker({ hsv, hsl, onChange, value: color }: ColorPickerProps) {
const [value, setValue] = useState(color);
const input = useRef<HTMLInputElement>(null);
useEffect(() => {
if (color !== value && input.current !== document.activeElement) {
setValue(color === 'currentColor' ? color : String(color).toUpperCase());
}
}, [color]);
const handleChange = (e) => {
let value = e.target.value;
setValue(value);
onChange(value, e);
};
return (
<div>
<FormLabel htmlFor="color" fontWeight={'bold'}>
Color
</FormLabel>
<InputGroup>
<InputLeftElement
children={
<Icon>
<rect x={0} width={24} y={0} height={24} fill={value} rx={2} />
</Icon>
}
/>
<Input value={value} name="color" onChange={handleChange} ref={input} />
</InputGroup>
<div
style={{
width: '100%',
paddingBottom: '75%',
position: 'relative',
overflow: 'hidden',
marginTop: '0.5rem',
borderRadius: '0.375rem',
border: '1px solid',
borderColor: 'inherit',
}}
>
<Saturation hsl={hsl} hsv={hsv} onChange={onChange} />
</div>
<div
style={{
minHeight: '2em',
position: 'relative',
margin: '0.5rem 0 0 0',
borderRadius: '0.375rem',
border: '1px solid',
borderColor: 'inherit',
overflow: 'hidden',
}}
>
<Hue hsl={hsl} onChange={onChange} direction={'horizontal'} />
</div>
</div>
);
}
Example #10
Source File: SearchInput.tsx From lucide with ISC License | 5 votes |
SearchInput = (
({ onChange, count }: SearchInputProps) => {
const { colorMode } = useColorMode();
const [urlValue, setUrlValue] = useRouterParam('search');
const [inputValue, setInputValue] = useState('');
const debouncedValue = useDebounce(inputValue.trim(), 300);
useUpdateEffect(() => {
onChange(debouncedValue);
setUrlValue(debouncedValue);
}, [debouncedValue]);
useEffect(() => {
if (urlValue && !inputValue) {
setInputValue(urlValue);
onChange(urlValue);
}
}, [urlValue]);
const ref = useRef(null);
// Keyboard `/` shortcut
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === '/' && ref.current !== document.activeElement) {
event.preventDefault();
ref.current.focus();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
return (
<InputGroup position="sticky" top={4} zIndex={1}>
<InputLeftElement
children={
<Icon>
<SearchIcon />
</Icon>
}
/>
<Input
ref={ref}
placeholder={`Search ${count} icons (Press "/" to focus)`}
onChange={(event) => setInputValue(event.target.value)}
value={inputValue}
bg={colorMode == 'light' ? theme.colors.white : theme.colors.gray[700]}
/>
</InputGroup>
);
}
)
Example #11
Source File: ContactsLayout.tsx From bluebubbles-server with Apache License 2.0 | 4 votes |
ContactsLayout = (): JSX.Element => {
const [search, setSearch] = useState('' as string);
const [isLoading, setIsLoading] = useBoolean(true);
const [contacts, setContacts] = useState([] as any[]);
const [permission, setPermission] = useState((): string | null => {
return null;
});
const dialogRef = useRef(null);
const inputFile = useRef(null);
const [dialogOpen, setDialogOpen] = useBoolean();
const alertRef = useRef(null);
const [requiresConfirmation, confirm] = useState((): string | null => {
return null;
});
let filteredContacts = contacts;
if (search && search.length > 0) {
filteredContacts = filteredContacts.filter((c) => buildIdentifier(c).includes(search.toLowerCase()));
}
const {
currentPage,
setCurrentPage,
pagesCount,
pages
} = usePagination({
pagesCount: Math.ceil(filteredContacts.length / perPage),
initialState: { currentPage: 1 },
});
const refreshPermissionStatus = async (): Promise<void> => {
setPermission(null);
await waitMs(500);
ipcRenderer.invoke('contact-permission-status').then((status: string) => {
setPermission(status);
}).catch(() => {
setPermission('Unknown');
});
};
const requestContactPermission = async (): Promise<void> => {
setPermission(null);
ipcRenderer.invoke('request-contact-permission').then((status: string) => {
setPermission(status);
}).catch(() => {
setPermission('Unknown');
});
};
const loadContacts = (showToast = false) => {
ipcRenderer.invoke('get-contacts').then((contactList: any[]) => {
setContacts(contactList.map((e: any) => {
// Patch the ID as a string
e.id = String(e.id);
return e;
}));
setIsLoading.off();
}).catch(() => {
setIsLoading.off();
});
if (showToast) {
showSuccessToast({
id: 'contacts',
description: 'Successfully refreshed Contacts!'
});
}
};
useEffect(() => {
loadContacts();
refreshPermissionStatus();
}, []);
const getEmptyContent = () => {
const wrap = (child: JSX.Element) => {
return (
<section style={{marginTop: 20}}>
{child}
</section>
);
};
if (isLoading) {
return wrap(<CircularProgress isIndeterminate />);
}
if (contacts.length === 0) {
return wrap(<Text fontSize="md">BlueBubbles found no contacts in your Mac's Address Book!</Text>);
}
return null;
};
const filterContacts = () => {
return filteredContacts.slice((currentPage - 1) * perPage, currentPage * perPage);
};
const onCreate = async (contact: ContactItem) => {
const newContact = await createContact(
contact.firstName,
contact.lastName,
{
emails: contact.emails.map((e: NodeJS.Dict<any>) => e.address),
phoneNumbers: contact.phoneNumbers.map((e: NodeJS.Dict<any>) => e.address)
}
);
if (newContact) {
// Patch the contact using a string ID & source type
newContact.id = String(newContact.id);
newContact.sourceType = 'db';
// Patch the addresses
(newContact as any).phoneNumbers = (newContact as any).addresses.filter((e: any) => e.type === 'phone');
(newContact as any).emails = (newContact as any).addresses.filter((e: any) => e.type === 'email');
setContacts([newContact, ...contacts]);
}
};
const onUpdate = async (contact: NodeJS.Dict<any>) => {
const cId = typeof(contact.id) === 'string' ? Number.parseInt(contact.id) : contact.id as number;
const newContact = await updateContact(
cId,
{
firstName: contact.firstName,
lastName: contact.lastName,
displayName: contact.displayName
}
);
const copiedContacts = [...contacts];
let updated = false;
for (let i = 0; i < copiedContacts.length; i++) {
if (copiedContacts[i].id === String(cId)) {
copiedContacts[i].firstName = newContact.firstName;
copiedContacts[i].lastName = newContact.lastName;
copiedContacts[i].displayName = newContact.displayName;
updated = true;
}
}
if (updated) {
setContacts(copiedContacts);
}
};
const onDelete = async (contactId: number | string) => {
await deleteContact(typeof(contactId) === 'string' ? Number.parseInt(contactId as string) : contactId);
setContacts(contacts.filter((e: ContactItem) => {
return e.id !== String(contactId);
}));
};
const onAddAddress = async (contactId: number | string, address: string) => {
const cId = typeof(contactId) === 'string' ? Number.parseInt(contactId as string) : contactId;
const addr = await addAddressToContact(cId, address, address.includes('@') ? 'email' : 'phone');
if (addr) {
setContacts(contacts.map((e: ContactItem) => {
if (e.id !== String(contactId)) return e;
if (address.includes('@')) {
e.emails = [...e.emails, addr];
} else {
e.phoneNumbers = [...e.phoneNumbers, addr];
}
return e;
}));
}
};
const onDeleteAddress = async (contactAddressId: number) => {
await deleteContactAddress(contactAddressId);
setContacts(contacts.map((e: ContactItem) => {
e.emails = e.emails.filter((e: ContactAddress) => e.id !== contactAddressId);
e.phoneNumbers = e.phoneNumbers.filter((e: ContactAddress) => e.id !== contactAddressId);
return e;
}));
};
const clearLocalContacts = async () => {
// Delete the contacts, then filter out the DB items
await deleteLocalContacts();
setContacts(contacts.filter(e => e.sourceType !== 'db'));
};
const confirmationActions: ConfirmationItems = {
clearLocalContacts: {
message: (
'Are you sure you want to clear/delete all local Contacts?<br /><br />' +
'This will remove any Contacts added manually, via the API, or via the import process'
),
func: clearLocalContacts
}
};
return (
<Box p={3} borderRadius={10}>
<Stack direction='column' p={5}>
<Text fontSize='2xl'>Controls</Text>
<Divider orientation='horizontal' />
<Box>
<Menu>
<MenuButton
as={Button}
rightIcon={<BsChevronDown />}
width="12em"mr={5}
>
Manage
</MenuButton>
<MenuList>
<MenuItem icon={<BsPersonPlus />} onClick={() => setDialogOpen.on()}>
Add Contact
</MenuItem>
<MenuItem icon={<BiRefresh />} onClick={() => loadContacts(true)}>
Refresh Contacts
</MenuItem>
<MenuItem
icon={<BiImport />}
onClick={() => {
if (inputFile && inputFile.current) {
(inputFile.current as HTMLElement).click();
}
}}
>
Import VCF
<input
type='file'
id='file'
ref={inputFile}
accept=".vcf"
style={{display: 'none'}}
onChange={(e) => {
const files = e?.target?.files ?? [];
for (const i of files) {
ipcRenderer.invoke('import-vcf', i.webkitRelativePath);
}
}}
/>
</MenuItem>
<MenuDivider />
<MenuItem icon={<FiTrash />} onClick={() => confirm('clearLocalContacts')}>
Clear Local Contacts
</MenuItem>
</MenuList>
</Menu>
<Menu>
<MenuButton
as={Button}
rightIcon={<BsChevronDown />}
width="12em"
mr={5}
>
Permissions
</MenuButton>
<MenuList>
<MenuItem icon={<BiRefresh />} onClick={() => refreshPermissionStatus()}>
Refresh Permission Status
</MenuItem>
{(permission !== null && permission !== 'Authorized') ? (
<MenuItem icon={<BsUnlockFill />} onClick={() => requestContactPermission()}>
Request Permission
</MenuItem>
) : null}
</MenuList>
</Menu>
<Text as="span" verticalAlign="middle">
Status: <Text as="span" color={getPermissionColor(permission)}>
{permission ? permission : 'Checking...'}
</Text>
</Text>
</Box>
</Stack>
<Stack direction='column' p={5}>
<Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
<Text fontSize='2xl'>Contacts ({filteredContacts.length})</Text>
<Popover trigger='hover'>
<PopoverTrigger>
<Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
<AiOutlineInfoCircle />
</Box>
</PopoverTrigger>
<PopoverContent>
<PopoverArrow />
<PopoverCloseButton />
<PopoverHeader>Information</PopoverHeader>
<PopoverBody>
<Text>
Here are the contacts on your macOS device that BlueBubbles knows about,
and will serve to any clients that want to know about them. These include
contacts from this Mac's Address Book, as well as contacts from uploads/imports
or manual entry.
</Text>
</PopoverBody>
</PopoverContent>
</Popover>
</Flex>
<Divider orientation='horizontal' />
<Flex flexDirection='row' justifyContent='flex-end' alignItems='center' pt={3}>
<InputGroup width="xxs">
<InputLeftElement pointerEvents='none'>
<AiOutlineSearch color='gray.300' />
</InputLeftElement>
<Input
placeholder='Search Contacts'
onChange={(e) => {
if (currentPage > 1) {
setCurrentPage(1);
}
setSearch(e.target.value);
}}
value={search}
/>
</InputGroup>
</Flex>
<Flex justifyContent="center" alignItems="center">
{getEmptyContent()}
</Flex>
{(contacts.length > 0) ? (
<ContactsTable
contacts={filterContacts()}
onCreate={onCreate}
onDelete={onDelete}
onUpdate={onUpdate}
onAddressAdd={onAddAddress}
onAddressDelete={onDeleteAddress}
/>
) : null}
<Pagination
pagesCount={pagesCount}
currentPage={currentPage}
onPageChange={setCurrentPage}
>
<PaginationContainer
align="center"
justify="space-between"
w="full"
pt={2}
>
<PaginationPrevious minWidth={'75px'}>Previous</PaginationPrevious>
<Box ml={1}></Box>
<PaginationPageGroup flexWrap="wrap" justifyContent="center">
{pages.map((page: number) => (
<PaginationPage
key={`pagination_page_${page}`}
page={page}
my={1}
px={3}
fontSize={14}
/>
))}
</PaginationPageGroup>
<Box ml={1}></Box>
<PaginationNext minWidth={'50px'}>Next</PaginationNext>
</PaginationContainer>
</Pagination>
</Stack>
<ContactDialog
modalRef={dialogRef}
isOpen={dialogOpen}
onCreate={onCreate}
onDelete={onDelete}
onAddressAdd={onAddAddress}
onAddressDelete={onDeleteAddress}
onClose={() => setDialogOpen.off()}
/>
<ConfirmationDialog
modalRef={alertRef}
onClose={() => confirm(null)}
body={confirmationActions[requiresConfirmation as string]?.message}
onAccept={() => {
confirmationActions[requiresConfirmation as string].func();
}}
isOpen={requiresConfirmation !== null}
/>
</Box>
);
}
Example #12
Source File: TokenSelect.tsx From rari-dApp with GNU Affero General Public License v3.0 | 4 votes |
TokenSelect = ({
onSelectToken: _onSelectToken,
onClose,
mode,
}: {
mode: Mode;
onClose: () => any;
onSelectToken: (symbol: string) => any;
}) => {
const [searchNeedle, setSearchNeedle] = useState("");
const poolType = usePoolType();
const noSlippageCurrencies = useNoSlippageCurrencies(poolType);
const tokenKeys = (() => {
if (poolType === Pool.ETH) {
return ["ETH"];
}
return searchNeedle === ""
? Object.keys(tokens).sort((a, b) => {
// First items shown last, last items shown at the top!
const priorityCurrencies = [
"sUSD",
"WETH",
"ETH",
"DAI",
"mUSD",
"USDT",
"USDC",
];
if (priorityCurrencies.indexOf(a) < priorityCurrencies.indexOf(b)) {
return 1;
}
if (priorityCurrencies.indexOf(a) > priorityCurrencies.indexOf(b)) {
return -1;
}
return 0;
})
: Object.keys(tokens).filter((symbol) =>
symbol.toLowerCase().startsWith(searchNeedle.toLowerCase())
);
})();
const { t } = useTranslation();
return (
<Fade>
<ModalTitleWithCloseButton text={t("Select A Token")} onClose={onClose} />
<ModalDivider />
<Box px={4}>
<InputGroup mb={2}>
<InputLeftElement
ml={-1}
children={<SearchIcon color="gray.300" />}
/>
<Input
variant="flushed"
roundedLeft="0"
placeholder={t("Try searching for 'USDC'")}
focusBorderColor="#FFFFFF"
value={searchNeedle}
onChange={(event) => setSearchNeedle(event.target.value)}
/>
</InputGroup>
</Box>
<Box px={4}>
No Slippage:{" "}
<b>
{!noSlippageCurrencies
? " Loading..."
: noSlippageCurrencies.map((token: string, index: number) => {
return (
token +
(index === (noSlippageCurrencies as string[]).length - 1
? ""
: ", ")
);
})}
</b>
</Box>
<Box
pt={2}
px={4}
width="100%"
height={{
md: poolHasDivergenceRisk(poolType) ? "182px" : "157px",
base: poolHasDivergenceRisk(poolType) ? "210px" : "170px",
}}
>
<TokenList
mode={mode}
tokenKeys={tokenKeys}
onClick={(symbol) => {
_onSelectToken(symbol);
onClose();
}}
/>
</Box>
</Fade>
);
}
Example #13
Source File: PaddingPanel.tsx From openchakra with MIT License | 4 votes |
PaddingPanel = ({ type }: PaddingPanelPropsType) => {
const { setValueFromEvent } = useForm()
const all = usePropsSelector(ATTRIBUTES[type].all)
const left = usePropsSelector(ATTRIBUTES[type].left)
const right = usePropsSelector(ATTRIBUTES[type].right)
const bottom = usePropsSelector(ATTRIBUTES[type].bottom)
const top = usePropsSelector(ATTRIBUTES[type].top)
return (
<Box mb={4}>
<FormControl>
<FormLabel fontSize="xs" htmlFor="width" textTransform="capitalize">
{type}
</FormLabel>
<InputGroup size="sm">
<Input
mb={1}
placeholder="All"
size="sm"
type="text"
name={ATTRIBUTES[type].all}
value={all || ''}
onChange={setValueFromEvent}
/>
</InputGroup>
<SimpleGrid columns={2} spacing={1}>
<InputGroup size="sm">
<InputLeftElement
children={
<ArrowBackIcon path="" fontSize="md" color="gray.300" />
}
/>
<Input
placeholder="left"
size="sm"
type="text"
name={ATTRIBUTES[type].left}
value={left || ''}
onChange={setValueFromEvent}
autoComplete="off"
/>
</InputGroup>
<InputGroup size="sm">
<InputLeftElement
children={
<ArrowForwardIcon path="" fontSize="md" color="gray.300" />
}
/>
<Input
placeholder="right"
size="sm"
type="text"
value={right || ''}
name={ATTRIBUTES[type].right}
onChange={setValueFromEvent}
autoComplete="off"
/>
</InputGroup>
<InputGroup size="sm">
<InputLeftElement
children={<ArrowUpIcon path="" fontSize="md" color="gray.300" />}
/>
<Input
placeholder="top"
size="sm"
type="text"
value={top || ''}
name={ATTRIBUTES[type].top}
onChange={setValueFromEvent}
autoComplete="off"
/>
</InputGroup>
<InputGroup size="sm">
<InputLeftElement
children={
<ChevronDownIcon path="" fontSize="md" color="gray.300" />
}
/>
<Input
placeholder="bottom"
size="sm"
type="text"
value={bottom || ''}
name={ATTRIBUTES[type].bottom}
onChange={setValueFromEvent}
autoComplete="off"
/>
</InputGroup>
</SimpleGrid>
</FormControl>
</Box>
)
}
Example #14
Source File: index.tsx From calories-in with MIT License | 4 votes |
function FoodsList({
selection,
searchInputRef,
onFoodPreview,
forwardedRef,
allowsFiltering = true,
itemUsageType = 'selectOrPreview',
...rest
}: Props) {
const { allFoods, userFoods } = useFoods()
const listRef = useRef<FixedSizeList>(null)
const filter = useFoodsFilter()
const foodsFilterActions = useFoodsFilterActions()
const filteredFoods = useFilterFoods(allFoods, userFoods, filter)
useEffect(() => {
if (filter.categoryId) {
listRef.current?.scrollToItem(0, 'start')
}
}, [filter.categoryId])
useImperativeHandle(forwardedRef, () => ({
scrollToFood: (food: Food) => {
foodsFilterActions.resetCategoryIdAndQuery()
if (listRef.current) {
const foods = filter.onlyFoodsAddedByUser ? userFoods : allFoods
const index = foods.map(({ id }) => id).indexOf(food.id)
listRef.current.scrollToItem(index, 'center')
}
},
}))
function onFoodSelect(food: Food) {
if (selection) {
selection.toggleItem(food)
const input = searchInputRef?.current
if (input && !isMobile) {
input.focus()
input.setSelectionRange(0, input.value.length)
}
}
}
return (
<Flex flexDirection="column" {...rest}>
<HStack spacing={3}>
{allowsFiltering && (
<Box>
<FoodsFilterPopoverOrModal />
</Box>
)}
<InputGroup size="md" flex={4}>
<InputLeftElement
pointerEvents="none"
children={
<SearchStyled pointerEvents="none" size={20} color="gray.400" />
}
/>
<Input
ref={searchInputRef}
value={filter.query}
onChange={(event: ChangeEvent<HTMLInputElement>) =>
foodsFilterActions.updateFilter({ query: event.target.value })
}
placeholder="Search"
/>
</InputGroup>
</HStack>
<Divider mt={3} width="100%" />
{filteredFoods.length > 0 ? (
<VirtualizedList
ref={listRef}
foodsCount={filteredFoods.length}
isFoodSelected={food =>
selection ? selection.isIdSelected(food.id) : false
}
getFood={index => filteredFoods[index]}
onFoodSelect={onFoodSelect}
onFoodPreview={onFoodPreview || (() => {})}
itemUsageType={itemUsageType}
/>
) : (
<Flex flex={1} alignItems="center" justifyContent="center">
<Text textColor="gray.500">No foods found</Text>
</Flex>
)}
</Flex>
)
}