diff --git a/packages/pl-fe/src/components/ui/input.tsx b/packages/pl-fe/src/components/ui/input.tsx index 6d3bb9d1e..28a47f70f 100644 --- a/packages/pl-fe/src/components/ui/input.tsx +++ b/packages/pl-fe/src/components/ui/input.tsx @@ -17,7 +17,7 @@ const messages = defineMessages({ /** Possible theme names for an Input. */ type InputThemes = 'normal' | 'search' | 'transparent' -interface IInput extends Pick, 'maxLength' | 'onChange' | 'onBlur' | 'type' | 'autoComplete' | 'autoCorrect' | 'autoCapitalize' | 'required' | 'disabled' | 'onClick' | 'readOnly' | 'min' | 'pattern' | 'onKeyDown' | 'onKeyUp' | 'onFocus' | 'onMouseDown' | 'style' | 'id' | 'lang'> { +interface IInput extends Pick, 'maxLength' | 'onChange' | 'onBlur' | 'type' | 'autoComplete' | 'autoCorrect' | 'autoCapitalize' | 'required' | 'disabled' | 'onClick' | 'readOnly' | 'min' | 'pattern' | 'onKeyDown' | 'onKeyUp' | 'onFocus' | 'onMouseDown' | 'style' | 'id' | 'lang' | 'title'> { /** Put the cursor into the input on mount. */ autoFocus?: boolean; /** The initial text in the input. */ diff --git a/packages/pl-fe/src/features/compose/components/language-dropdown.tsx b/packages/pl-fe/src/features/compose/components/language-dropdown.tsx index 465310e43..d81121c06 100644 --- a/packages/pl-fe/src/features/compose/components/language-dropdown.tsx +++ b/packages/pl-fe/src/features/compose/components/language-dropdown.tsx @@ -143,7 +143,7 @@ const getLanguageDropdown = (composeId: string): React.FC => }, [node.current]); const isSearching = searchValue !== ''; - const results = search(); + const results = useMemo(search, [searchValue]); return ( <> diff --git a/packages/pl-fe/src/locales/en.json b/packages/pl-fe/src/locales/en.json index a2bc75a6a..e10bbf136 100644 --- a/packages/pl-fe/src/locales/en.json +++ b/packages/pl-fe/src/locales/en.json @@ -289,6 +289,7 @@ "bookmark_folders.edit.success": "Bookmark folder edited successfully", "bookmark_folders.new.create_title": "Add folder", "bookmark_folders.new.title_placeholder": "New folder title", + "bookmark_folders.new.title_with_search_placeholder": "Search or create new folder", "bookmarks.delete_folder": "Delete folder", "bookmarks.delete_folder.fail": "Failed to delete folder", "bookmarks.delete_folder.success": "Folder deleted", diff --git a/packages/pl-fe/src/modals/edit-bookmark-folder-modal.tsx b/packages/pl-fe/src/modals/edit-bookmark-folder-modal.tsx index b87e8e038..d4d3c932d 100644 --- a/packages/pl-fe/src/modals/edit-bookmark-folder-modal.tsx +++ b/packages/pl-fe/src/modals/edit-bookmark-folder-modal.tsx @@ -151,16 +151,15 @@ const EditBookmarkFolderModal: React.FC )} - + ); diff --git a/packages/pl-fe/src/modals/select-bookmark-folder-modal.tsx b/packages/pl-fe/src/modals/select-bookmark-folder-modal.tsx index d9e5a2634..dd44ce4b2 100644 --- a/packages/pl-fe/src/modals/select-bookmark-folder-modal.tsx +++ b/packages/pl-fe/src/modals/select-bookmark-folder-modal.tsx @@ -1,4 +1,6 @@ -import React, { useCallback, useState } from 'react'; +import fuzzysort from 'fuzzysort'; +import { BookmarkFolder } from 'pl-api'; +import React, { useCallback, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { ListItem } from 'pl-fe/components/list'; @@ -23,12 +25,23 @@ interface SelectBookmarkFolderModalProps { statusId: string; } +const search = (bookmarkFolders: Array, term: string) => { + if (!term) return bookmarkFolders; + + return fuzzysort.go(term, bookmarkFolders, { key: 'name' }).map(result => result.obj); +}; + const SelectBookmarkFolderModal: React.FC = ({ statusId, onClose }) => { const getStatus = useCallback(makeGetStatus(), []); const status = useAppSelector(state => getStatus(state, { id: statusId }))!; const features = useFeatures(); const [selectedFolder, setSelectedFolder] = useState(status.bookmark_folder); + const [searchTerm, setSearchTerm] = useState(''); + + const handleSearchChange: React.ChangeEventHandler = e => { + setSearchTerm(e.target.value); + }; const { isFetching, data: bookmarkFolders } = useBookmarkFolders(data => data); const { data: selectedBookmarkFolders, isPending: fetchingSelectedBookmarkFolders } = useStatusBookmarkFolders(statusId); @@ -57,10 +70,18 @@ const SelectBookmarkFolderModal: React.FC { + if (!bookmarkFolders) return []; + + const filtered = search(bookmarkFolders, searchTerm); + + return filtered; + }, [bookmarkFolders, searchTerm]); + let items; if (features.bookmarkFoldersMultiple) { - items = (bookmarkFolders || []).map((folder) => ( + items = (filteredFolders).map((folder) => ( ( + items.push(...((filteredFolders).map((folder) => ( : ( - + {items} diff --git a/packages/pl-fe/src/pages/status-lists/bookmark-folders.tsx b/packages/pl-fe/src/pages/status-lists/bookmark-folders.tsx index 641468b2c..19e7336e6 100644 --- a/packages/pl-fe/src/pages/status-lists/bookmark-folders.tsx +++ b/packages/pl-fe/src/pages/status-lists/bookmark-folders.tsx @@ -20,17 +20,28 @@ import toast from 'pl-fe/toast'; const messages = defineMessages({ heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, label: { id: 'bookmark_folders.new.title_placeholder', defaultMessage: 'New folder title' }, + labelWithSearch: { id: 'bookmark_folders.new.title_with_search_placeholder', defaultMessage: 'Search or create new folder' }, createSuccess: { id: 'bookmark_folders.add.success', defaultMessage: 'Bookmark folder created successfully' }, createFail: { id: 'bookmark_folders.add.fail', defaultMessage: 'Failed to create bookmark folder' }, }); -const NewFolderForm: React.FC = () => { +interface INewFolderForm { + search?: boolean; + onChange?: React.ChangeEventHandler; +} + +const NewFolderForm: React.FC = ({ search, onChange }) => { const intl = useIntl(); const name = useTextField(); const { mutate: createBookmarkFolder, isPending } = useCreateBookmarkFolder(); + const handleChange: React.ChangeEventHandler = (e) => { + name.onChange(e); + if (onChange) onChange(e); + }; + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); createBookmarkFolder({ @@ -45,21 +56,20 @@ const NewFolderForm: React.FC = () => { }); }; - const label = intl.formatMessage(messages.label); + const label = intl.formatMessage(search ? messages.labelWithSearch : messages.label); return (
- +