From e465b45d6ecc965c56793437ab2057f949e97f6e Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Sat, 15 Jun 2024 02:00:21 -0700 Subject: [PATCH] Mobile: Resolves #10592: Make mobile plugin settings screen UI closer to desktop (#10598) --- .eslintignore | 4 +- .gitignore | 4 +- .../plugins/PluginBox/PluginChip.tsx | 57 ++++++++++++ .../plugins/PluginBox/PluginChips.tsx | 57 +++--------- .../plugins/PluginBox/RecommendedBadge.tsx | 74 ++++++++++++++++ .../plugins/PluginBox/StyledChip.tsx | 39 --------- .../ConfigScreen/plugins/PluginBox/index.tsx | 12 ++- .../plugins/PluginStates.installed.test.tsx | 14 +-- .../plugins/PluginStates.search.test.tsx | 12 +-- .../ConfigScreen/plugins/PluginStates.tsx | 86 ++++++++----------- .../ConfigScreen/plugins/SearchPlugins.tsx | 30 ++++--- .../ConfigScreen/plugins/SectionLabel.tsx | 24 ++++++ .../plugins/testUtils/WrappedPluginStates.tsx | 21 +++-- 13 files changed, 248 insertions(+), 186 deletions(-) create mode 100644 packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.tsx create mode 100644 packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx delete mode 100644 packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.tsx create mode 100644 packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.tsx diff --git a/.eslintignore b/.eslintignore index 87ebb2e3d..6df412a8e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -619,9 +619,10 @@ packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js +packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js -packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.js +packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js @@ -629,6 +630,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search. packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js +packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js diff --git a/.gitignore b/.gitignore index a12f80d1f..a990d2f41 100644 --- a/.gitignore +++ b/.gitignore @@ -598,9 +598,10 @@ packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js +packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js -packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.js +packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js @@ -608,6 +609,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search. packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js +packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.tsx new file mode 100644 index 000000000..d12aa976e --- /dev/null +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { Chip, ChipProps } from 'react-native-paper'; +import { useMemo } from 'react'; +import { connect } from 'react-redux'; +import { AppState } from '../../../../../utils/types'; +import { themeStyle } from '../../../../global-style'; + +type Props = { + themeId: number; + color?: string; + faded?: boolean; + onPress?: ()=> void; + icon?: string; + children: React.ReactNode; +}; + +const fadedStyle = { opacity: 0.87 }; + +const PluginChip: React.FC = props => { + const themeOverride = useMemo(() => { + const theme = themeStyle(props.themeId); + const foreground = props.color ?? theme.color; + const background = theme.backgroundColor; + return { + colors: { + secondaryContainer: background, + onSecondaryContainer: foreground, + primary: foreground, + + outline: foreground, + onPrimary: foreground, + onSurfaceVariant: foreground, + }, + }; + }, [props.themeId, props.color]); + + const accessibilityProps: Partial = {}; + if (!props.onPress) { + // Note: May have no effect until a future version of RN Paper. + // See https://github.com/callstack/react-native-paper/pull/4327 + accessibilityProps.accessibilityRole = 'text'; + } + + return ; +}; + +export default connect((state: AppState) => { + return { + themeId: state.settings.theme, + }; +})(PluginChip); diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx index cffaccd36..9002609d3 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx @@ -2,10 +2,10 @@ import { PluginItem } from '@joplin/lib/components/shared/config/plugins/types'; import PluginService from '@joplin/lib/services/plugins/PluginService'; import shim from '@joplin/lib/shim'; import * as React from 'react'; -import { Alert, Linking, View, ViewStyle } from 'react-native'; +import { View, ViewStyle } from 'react-native'; import { _ } from '@joplin/lib/locale'; import { PluginCallback } from '../utils/usePluginCallbacks'; -import StyledChip from './StyledChip'; +import PluginChip from './PluginChip'; import { themeStyle } from '../../../../global-style'; interface Props { @@ -19,23 +19,6 @@ interface Props { onShowPluginLog?: PluginCallback; } -const onRecommendedPress = () => { - Alert.alert( - '', - _('The Joplin team has vetted this plugin and it meets our standards for security and performance.'), - [ - { - text: _('Learn more'), - onPress: () => Linking.openURL('https://github.com/joplin/plugins/blob/master/readme/recommended.md'), - }, - { - text: _('OK'), - }, - ], - { cancelable: true }, - ); -}; - const containerStyle: ViewStyle = { flexDirection: 'row', gap: 4, @@ -54,43 +37,28 @@ const PluginChips: React.FC = props => { if (!props.hasErrors) return null; return ( - props.onShowPluginLog({ item })} > {_('Error')} - + ); }; - const renderRecommendedChip = () => { - if (!props.item.manifest._recommended || !props.isCompatible) { - return null; - } - return {_('Recommended')}; - }; - const renderBuiltInChip = () => { if (!props.item.builtIn) { return null; } - return {_('Built-in')}; + return {_('Built-in')}; }; const renderIncompatibleChip = () => { if (props.isCompatible) return null; return ( - { void shim.showMessageBox( @@ -98,7 +66,7 @@ const PluginChips: React.FC = props => { { buttons: [_('OK')] }, ); }} - >{_('Incompatible')} + >{_('Incompatible')} ); }; @@ -106,7 +74,7 @@ const PluginChips: React.FC = props => { if (!props.isCompatible || !props.canUpdate) return null; return ( - {_('Update available')} + {_('Update available')} ); }; @@ -114,21 +82,20 @@ const PluginChips: React.FC = props => { if (props.item.enabled || !props.item.installed) { return null; } - return {_('Disabled')}; + return {_('Disabled')}; }; const renderInstalledChip = () => { if (!props.showInstalledChip) { return null; } - return {_('Installed')}; + return {_('Installed')}; }; return {renderIncompatibleChip()} {renderInstalledChip()} {renderErrorsChip()} - {renderRecommendedChip()} {renderBuiltInChip()} {renderUpdatableChip()} {renderDisabledChip()} diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx new file mode 100644 index 000000000..d66abb318 --- /dev/null +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx @@ -0,0 +1,74 @@ +import { _ } from '@joplin/lib/locale'; +import { PluginManifest } from '@joplin/lib/services/plugins/utils/types'; +import * as React from 'react'; +import IconButton from '../../../../IconButton'; +import { Alert, Linking, StyleSheet } from 'react-native'; +import { themeStyle } from '../../../../global-style'; +import { useMemo } from 'react'; + +const onRecommendedPress = () => { + Alert.alert( + '', + _('The Joplin team has vetted this plugin and it meets our standards for security and performance.'), + [ + { + text: _('Learn more'), + onPress: () => Linking.openURL('https://github.com/joplin/plugins/blob/master/readme/recommended.md'), + }, + { + text: _('OK'), + }, + ], + { cancelable: true }, + ); +}; + +interface Props { + themeId: number; + manifest: PluginManifest; + isCompatible: boolean; +} + +const useStyles = (themeId: number) => { + return useMemo(() => { + const theme = themeStyle(themeId); + return StyleSheet.create({ + container: { + opacity: 0.8, + }, + wrapper: { + borderColor: theme.colorWarn, + borderWidth: 1, + borderRadius: 20, + justifyContent: 'center', + height: 32, + width: 32, + textAlign: 'center', + }, + icon: { + fontSize: 14, + color: theme.colorWarn, + marginLeft: 'auto', + marginRight: 'auto', + }, + }); + }, [themeId]); +}; + +const RecommendedBadge: React.FC = props => { + const styles = useStyles(props.themeId); + + if (!props.manifest._recommended || !props.isCompatible) return null; + + return ; +}; + +export default RecommendedBadge; diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.tsx deleted file mode 100644 index feb33ede6..000000000 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react'; -import { Chip, ChipProps } from 'react-native-paper'; -import { useMemo } from 'react'; - -type Props = ({ - foreground: string; - background: string; -}|{ - foreground?: undefined; - background?: undefined; -}) & ChipProps; - -const RecommendedChip: React.FC = props => { - const themeOverride = useMemo(() => { - if (!props.foreground) return {}; - return { - colors: { - secondaryContainer: props.background, - onSecondaryContainer: props.foreground, - primary: props.foreground, - }, - }; - }, [props.foreground, props.background]); - - const accessibilityProps: Partial = {}; - if (!props.onPress) { - // Note: May have no effect until a future version of RN Paper. - // See https://github.com/callstack/react-native-paper/pull/4327 - accessibilityProps.accessibilityRole = 'text'; - } - - return ; -}; - -export default RecommendedChip; diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.tsx index 34c369ac9..7f7c9e5ae 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.tsx @@ -8,9 +8,10 @@ import PluginChips from './PluginChips'; import { UpdateState } from '../utils/useUpdateState'; import { PluginCallback } from '../utils/usePluginCallbacks'; import { useCallback, useMemo } from 'react'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import InstallButton from '../buttons/InstallButton'; import PluginTitle from './PluginTitle'; +import RecommendedBadge from './RecommendedBadge'; export enum InstallState { NotInstalled, @@ -92,8 +93,13 @@ const PluginBox: React.FC = props => { testID='plugin-card' > - - {manifest.description} + + + + {manifest.description} + + + { - const installedTab = await screen.findByText('Installed plugins'); - await userEvent.press(installedTab); -}; - const abcPluginId = 'org.joplinapp.plugins.AbcSheetMusic'; const backlinksPluginId = 'joplin.plugin.ambrt.backlinksToNote'; @@ -99,7 +94,6 @@ describe('PluginStates.installed', () => { store={reduxStore} />, ); - await showInstalledTab(); expect(await screen.findByText(/^ABC Sheet Music/)).toBeVisible(); expect(await screen.findByText(/^Backlinks to note/)).toBeVisible(); @@ -129,7 +123,6 @@ describe('PluginStates.installed', () => { store={reduxStore} />, ); - await showInstalledTab(); const abcSheetMusicCard = await screen.findByText(/^ABC Sheet Music/); expect(abcSheetMusicCard).toBeVisible(); @@ -150,7 +143,7 @@ describe('PluginStates.installed', () => { ); // Initially, no plugins should be installed - expect(screen.queryByText(/^You currently have 0 plugins? installed/)).toBeNull(); + expect(screen.queryByText('Installed (0):')).toBeNull(); const testPluginId1 = 'org.joplinapp.plugins.AbcSheetMusic'; const testPluginId2 = 'org.joplinapp.plugins.test.plugin.id'; @@ -158,8 +151,6 @@ describe('PluginStates.installed', () => { await act(() => loadMockPlugin(testPluginId2, 'A test plugin', '1.0.0', pluginSettings)); expect(PluginService.instance().plugins[testPluginId1]).toBeTruthy(); - await showInstalledTab(); - // Should update the list of installed plugins even though the plugin settings didn't change. expect(await screen.findByText(/^ABC Sheet Music/)).toBeVisible(); expect(await screen.findByText(/^A test plugin/)).toBeVisible(); @@ -184,7 +175,6 @@ describe('PluginStates.installed', () => { store={reduxStore} />, ); - await showInstalledTab(); const card = await screen.findByText('ABC Sheet Music'); const user = userEvent.setup(); @@ -226,7 +216,6 @@ describe('PluginStates.installed', () => { store={reduxStore} />, ); - await showInstalledTab(); // Open the plugin dialog const card = await screen.findByText('ABC Sheet Music'); @@ -279,7 +268,6 @@ describe('PluginStates.installed', () => { store={reduxStore} />, ); - await showInstalledTab(); // Should be shown as installed. const card = await screen.findByText('ABC Sheet Music'); diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.test.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.test.tsx index b5954708b..8f659292f 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.test.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.test.tsx @@ -18,14 +18,9 @@ const expectSearchResultCountToBe = async (count: number) => { }); }; -const showSearchTab = async () => { - const searchAccordion = await screen.findByText('Install new plugins'); - await userEvent.press(searchAccordion); -}; - // The search box is initially read-only -- waits for it to be editable. const getEditableSearchBox = async () => { - const searchBox = await screen.findByPlaceholderText('Search plugins'); + const searchBox = await screen.findByPlaceholderText('Search for plugins...'); expect(searchBox).toBeVisible(); await waitFor(() => { @@ -53,7 +48,6 @@ describe('PluginStates.search', () => { const wrapper = render(); const user = userEvent.setup(); - await showSearchTab(); const searchBox = await getEditableSearchBox(); await user.type(searchBox, 'backlinks'); @@ -81,8 +75,6 @@ describe('PluginStates.search', () => { const wrapper = render(); const user = userEvent.setup(); - await showSearchTab(); - const searchBox = await getEditableSearchBox(); await user.press(searchBox); @@ -111,8 +103,6 @@ describe('PluginStates.search', () => { const wrapper = render(); const user = userEvent.setup(); - await showSearchTab(); - const searchBox = await getEditableSearchBox(); await user.press(searchBox); await user.type(searchBox, 'abc'); diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx index c155e0daa..6102cd3c7 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { ConfigScreenStyles } from '../configScreenStyles'; import { View, StyleSheet } from 'react-native'; -import { Banner, Text, Button, ProgressBar, List, Divider } from 'react-native-paper'; -import { _, _n } from '@joplin/lib/locale'; +import { Banner, Text, Button, ProgressBar, Divider } from 'react-native-paper'; +import { _ } from '@joplin/lib/locale'; import PluginService, { PluginSettings, SerializedPluginSettings } from '@joplin/lib/services/plugins/PluginService'; import InstalledPluginBox from './InstalledPluginBox'; import SearchPlugins from './SearchPlugins'; @@ -13,6 +13,7 @@ import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi'; import PluginInfoModal from './PluginInfoModal'; import usePluginCallbacks from './utils/usePluginCallbacks'; import BetaChip from '../../../BetaChip'; +import SectionLabel from './SectionLabel'; interface Props { themeId: number; @@ -59,8 +60,8 @@ const useLoadedPluginIds = () => { const styles = StyleSheet.create({ installedPluginsContainer: { - marginLeft: 8, - marginRight: 8, + marginLeft: 12, + marginRight: 12, marginBottom: 10, }, }); @@ -134,11 +135,16 @@ const PluginStates: React.FC = props => { const installedPluginCards = []; const pluginService = PluginService.instance(); + const [searchQuery, setSearchQuery] = useState(''); + const isPluginSearching = !!searchQuery; + const pluginIds = useLoadedPluginIds(); for (const pluginId of pluginIds) { const plugin = pluginService.plugins[pluginId]; - if (!props.shouldShowBasedOnSearchQuery || props.shouldShowBasedOnSearchQuery(plugin.manifest.name)) { + const matchesGlobalSearch = !props.shouldShowBasedOnSearchQuery || props.shouldShowBasedOnSearchQuery(plugin.manifest.name); + const showCard = !isPluginSearching && matchesGlobalSearch; + if (showCard) { installedPluginCards.push( = props => { !props.shouldShowBasedOnSearchQuery || props.shouldShowBasedOnSearchQuery(searchInputSearchText()) ); - const [searchQuery, setSearchQuery] = useState(''); + const searchSection = ( + - - + searchQuery={searchQuery} + setSearchQuery={setSearchQuery} + /> ); - const isSearching = !!props.shouldShowBasedOnSearchQuery; - // Don't include the number of installed plugins when searching -- only a few of the total - // may be shown by the search. - const installedAccordionDescription = !isSearching ? _n('You currently have %d plugin installed.', 'You currently have %d plugins installed.', pluginIds.length, pluginIds.length) : null; - - // Using a different wrapper prevents the installed item group from being openable when - // there are no plugins: - const InstalledItemWrapper = pluginIds.length ? List.Accordion : List.Item; + const isSearching = !!props.shouldShowBasedOnSearchQuery || isPluginSearching; return ( @@ -202,20 +193,14 @@ const PluginStates: React.FC = props => { - - - - {installedPluginCards} - - - - {showSearch ? searchAccordion : null} - - + {showSearch ? searchSection : null} + + + {pluginIds.length ? _('Installed (%d):', pluginIds.length) : _('No plugins are installed.')} + + {installedPluginCards} + + = props => { onModalDismiss={onPluginDialogClosed} pluginCallbacks={pluginCallbacks} /> + ); }; diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx index 263a3fabb..6dd8d5348 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx @@ -5,7 +5,7 @@ import { _ } from '@joplin/lib/locale'; import { PluginManifest } from '@joplin/lib/services/plugins/utils/types'; import { useCallback, useMemo, useState } from 'react'; import { FlatList, StyleSheet, View } from 'react-native'; -import { TextInput, Text } from 'react-native-paper'; +import { TextInput } from 'react-native-paper'; import PluginBox, { InstallState } from './PluginBox'; import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService'; import { PluginItem } from '@joplin/lib/components/shared/config/plugins/types'; @@ -13,6 +13,7 @@ import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi'; import openWebsiteForPlugin from './utils/openWebsiteForPlugin'; import { PluginCallback, PluginCallbacks } from './utils/usePluginCallbacks'; import InstalledPluginBox from './InstalledPluginBox'; +import SectionLabel from './SectionLabel'; interface Props { themeId: number; @@ -42,14 +43,11 @@ const styles = StyleSheet.create({ container: { flexDirection: 'column', margin: 12, - }, - resultsCounter: { - margin: 12, - marginTop: 17, - marginBottom: 4, + marginBottom: 0, }, }); + const PluginSearch: React.FC = props => { const { searchQuery, setSearchQuery } = props; const [searchResultManifests, setSearchResultManifests] = useState([]); @@ -133,12 +131,16 @@ const PluginSearch: React.FC = props => { } }, [onInstall, props.themeId, props.pluginSettings, props.updatingPluginIds, props.updatablePluginIds, props.onShowPluginInfo, props.callbacks]); - const renderResultsCount = () => { - if (!searchQuery.length) return null; + const onClearSearch = useCallback(() => { + setSearchQuery(''); + }, [setSearchQuery]); - return - {_('Results (%d):', searchResults.length)} - ; + const renderSearchButton = () => { + if (searchQuery) { + return ; + } else { + return ; + } }; return ( @@ -146,13 +148,13 @@ const PluginSearch: React.FC = props => { } - placeholder={_('Search plugins')} + right={renderSearchButton()} + placeholder={_('Search for plugins...')} onChangeText={setSearchQuery} value={searchQuery} editable={props.repoApiInitialized} /> - {renderResultsCount()} + {_('Results (%d):', searchResults.length)} = props => { + if (!props.visible) return null; + + return + {props.children} + ; +}; + +export default SectionLabel; diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.tsx index c4d2abe54..5f93ed7f9 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.tsx @@ -8,6 +8,7 @@ import { PaperProvider } from 'react-native-paper'; import PluginStates from '../PluginStates'; import { AppState } from '../../../../../utils/types'; import { useCallback, useState } from 'react'; +import { MenuProvider } from 'react-native-popup-menu'; interface WrapperProps { initialPluginSettings: PluginSettings; @@ -29,15 +30,17 @@ const PluginStatesWrapper = (props: WrapperProps) => { return ( - - - + + + + + ); };