From 754ab493e60af2cc9fdc604a9a83f4870db2c9c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 13 Nov 2024 11:43:20 +0100 Subject: [PATCH 01/12] Refactor ConnectionsNavigation into NavigationItemsFilter --- .../connections-navigation.tsx | 70 +++--------- .../multiple-connections/sidebar.tsx | 23 ++-- .../components/navigation-items-filter.tsx | 104 ++++++++++++++---- .../components/use-filtered-connections.ts | 23 ++-- 4 files changed, 116 insertions(+), 104 deletions(-) diff --git a/packages/compass-sidebar/src/components/multiple-connections/connections-navigation.tsx b/packages/compass-sidebar/src/components/multiple-connections/connections-navigation.tsx index 00438b6d9e7..b98833e27d2 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/connections-navigation.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/connections-navigation.tsx @@ -12,10 +12,6 @@ import { Button, Icon, ButtonVariant, - IconButton, - ConnectedPlugsIcon, - DisconnectedPlugIcon, - Tooltip, } from '@mongodb-js/compass-components'; import { ConnectionsNavigationTree } from '@mongodb-js/compass-connections-navigation'; import type { MapDispatchToProps, MapStateToProps } from 'react-redux'; @@ -46,7 +42,10 @@ import { fetchAllCollections, type Database, } from '../../modules/databases'; -import { useFilteredConnections } from '../use-filtered-connections'; +import { + type ConnectionsFilter, + useFilteredConnections, +} from '../use-filtered-connections'; import NavigationItemsFilter from '../navigation-items-filter'; import { type ConnectionImportExportAction, @@ -84,15 +83,6 @@ const connectionCountStyles = css({ marginLeft: spacing[100], }); -const filterContainerStyles = css({ - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - gap: spacing[200], - paddingLeft: spacing[400], - paddingRight: spacing[400], -}); - const searchFormStyles = css({ flexGrow: 1, }); @@ -123,10 +113,10 @@ type ConnectionListTitleActions = type ConnectionsNavigationComponentProps = { connectionsWithStatus: ReturnType; activeWorkspace: WorkspaceTab | null; - filterRegex: RegExp | null; - excludeInactive: boolean; - onFilterChange(regex: RegExp | null): void; - onToggleExcludeInactive(): void; + filter: ConnectionsFilter; + onFilterChange( + updater: (filter: ConnectionsFilter) => ConnectionsFilter + ): void; onConnect(info: ConnectionInfo): void; onNewConnection(): void; onEditConnection(info: ConnectionInfo): void; @@ -167,13 +157,11 @@ type ConnectionsNavigationProps = ConnectionsNavigationComponentProps & const ConnectionsNavigation: React.FC = ({ connectionsWithStatus, activeWorkspace, - filterRegex, - excludeInactive, + filter, instances, databases, isPerformanceTabSupported, onFilterChange, - onToggleExcludeInactive, onConnect, onNewConnection, onEditConnection, @@ -270,10 +258,9 @@ const ConnectionsNavigation: React.FC = ({ onDatabaseToggle, } = useFilteredConnections({ connections, - filterRegex, + filter, fetchAllCollections, onDatabaseExpand, - excludeInactive, }); const connectionListTitleActions = @@ -519,37 +506,12 @@ const ConnectionsNavigation: React.FC = ({ {connections.length > 0 && ( <> -
- - - {excludeInactive ? ( - - ) : ( - - )} - - } - > - {excludeInactive - ? 'Showing active connections' - : 'Showing all connections'} - -
+ (undefined); - const [activeConnectionsFilterRegex, setActiveConnectionsFilterRegex] = - useState(null); + const [connectionsFilter, setConnectionsFilter] = useState( + { regex: null, excludeInactive: false } + ); const [connectionInfoModalConnectionId, setConnectionInfoModalConnectionId] = useState(); - const [excludeInactive, setExcludeInactiveConnections] = useState(false); - const toggleExcludeInactiveConnections = useCallback(() => { - setExcludeInactiveConnections((previous) => !previous); - }, []); const formPreferences = useConnectionFormPreferences(); const maybeProtectConnectionString = useMaybeProtectConnectionString(); @@ -142,12 +141,6 @@ export function MultipleConnectionSidebar({ )?.connectionInfo; }; - const onActiveConnectionFilterChange = useCallback( - (filterRegex: RegExp | null) => - setActiveConnectionsFilterRegex(filterRegex), - [setActiveConnectionsFilterRegex] - ); - const onOpenConnectionInfo = useCallback((connectionId: string) => { return setConnectionInfoModalConnectionId(connectionId); }, []); @@ -207,10 +200,8 @@ export function MultipleConnectionSidebar({ { void connect(connectionInfo); }} diff --git a/packages/compass-sidebar/src/components/navigation-items-filter.tsx b/packages/compass-sidebar/src/components/navigation-items-filter.tsx index 98c7abf4e57..2ea3348cc4c 100644 --- a/packages/compass-sidebar/src/components/navigation-items-filter.tsx +++ b/packages/compass-sidebar/src/components/navigation-items-filter.tsx @@ -1,51 +1,107 @@ import React, { useCallback } from 'react'; -import { TextInput } from '@mongodb-js/compass-components'; +import { + ConnectedPlugsIcon, + css, + DisconnectedPlugIcon, + IconButton, + spacing, + TextInput, + Tooltip, +} from '@mongodb-js/compass-components'; +import type { ConnectionsFilter } from './use-filtered-connections'; + +const filterContainerStyles = css({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: spacing[200], + paddingLeft: spacing[400], + paddingRight: spacing[400], +}); + +function createRegExp(input: string) { + try { + return input ? new RegExp(input, 'i') : null; + } catch (e) { + return null; + } +} export default function NavigationItemsFilter({ placeholder = 'Search', ariaLabel = 'Search', title = 'Search', + filter, onFilterChange, className, }: { placeholder?: string; ariaLabel?: string; title?: string; - onFilterChange(regex: RegExp | null): void; + filter: ConnectionsFilter; + onFilterChange( + updater: (filter: ConnectionsFilter) => ConnectionsFilter + ): void; className?: string; }): React.ReactElement { - const onChange = useCallback( + const onChange = useCallback>( (event) => { - const searchString: string = event.target.value; - - let re; - - try { - re = searchString ? new RegExp(searchString, 'i') : null; - } catch (e) { - re = null; - } - - onFilterChange(re); + onFilterChange((filter) => ({ + ...filter, + regex: createRegExp(event.target.value), + })); }, [onFilterChange] ); + const toggleExcludeInactive = useCallback(() => { + onFilterChange((filter) => ({ + ...filter, + excludeInactive: !filter.excludeInactive, + })); + }, [onFilterChange]); + const onSubmit = useCallback((evt) => { evt.preventDefault(); evt.stopPropagation(); }, []); return ( -
- - +
+
+ + + + {filter.excludeInactive ? ( + + ) : ( + + )} + + } + > + {filter.excludeInactive + ? 'Showing active connections' + : 'Showing all connections'} + +
); } diff --git a/packages/compass-sidebar/src/components/use-filtered-connections.ts b/packages/compass-sidebar/src/components/use-filtered-connections.ts index a90c6589c9f..e2ffb8b1384 100644 --- a/packages/compass-sidebar/src/components/use-filtered-connections.ts +++ b/packages/compass-sidebar/src/components/use-filtered-connections.ts @@ -388,16 +388,19 @@ function filteredConnectionsToSidebarConnection( return sidebarConnections; } +export type ConnectionsFilter = { + regex: RegExp | null; + excludeInactive: boolean; +}; + export const useFilteredConnections = ({ connections, - filterRegex, - excludeInactive, + filter, fetchAllCollections, onDatabaseExpand, }: { connections: SidebarConnection[]; - filterRegex: RegExp | null; - excludeInactive: boolean; + filter: ConnectionsFilter; fetchAllCollections: () => void; onDatabaseExpand: (connectionId: string, databaseId: string) => void; }): UseFilteredConnectionsHookResult => { @@ -423,9 +426,9 @@ export const useFilteredConnections = ({ // connections change often, but the effect only uses connections if the filter is active // so we use this conditional dependency to avoid too many calls const connectionsWhenFiltering = - (filterRegex || excludeInactive) && connections; + (filter.regex || filter.excludeInactive) && connections; useEffect(() => { - if (!filterRegex && !excludeInactive) { + if (!filter.regex && !filter.excludeInactive) { dispatch({ type: CLEAR_FILTER }); } else if (connectionsWhenFiltering) { // the above check is extra just to please TS @@ -438,13 +441,13 @@ export const useFilteredConnections = ({ dispatch({ type: FILTER_CONNECTIONS, connections: connectionsWhenFiltering, - filterRegex, - excludeInactive, + filterRegex: filter.regex, + excludeInactive: filter.excludeInactive, }); } }, [ - filterRegex, - excludeInactive, + filter.regex, + filter.excludeInactive, connectionsWhenFiltering, fetchAllCollections, ]); From 4fd49fcba43adce2e2768deda84083150d5a7400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 13 Nov 2024 14:30:39 +0100 Subject: [PATCH 02/12] Refactor to a popover --- .../components/connections-filter-popover.tsx | 104 ++++++++++++++++++ .../components/navigation-items-filter.tsx | 50 ++------- 2 files changed, 114 insertions(+), 40 deletions(-) create mode 100644 packages/compass-sidebar/src/components/connections-filter-popover.tsx diff --git a/packages/compass-sidebar/src/components/connections-filter-popover.tsx b/packages/compass-sidebar/src/components/connections-filter-popover.tsx new file mode 100644 index 00000000000..c4902adf19d --- /dev/null +++ b/packages/compass-sidebar/src/components/connections-filter-popover.tsx @@ -0,0 +1,104 @@ +import React, { useCallback, type PropsWithChildren } from 'react'; + +import { + css, + Icon, + IconButton, + InteractivePopover, + Label, + Overline, + spacing, + Toggle, + Tooltip, + useId, +} from '@mongodb-js/compass-components'; +import type { ConnectionsFilter } from './use-filtered-connections'; + +const containerStyles = css({ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + padding: spacing[300], + minWidth: 270, +}); + +const closeButtonStyles = css({ + display: 'none', +}); + +const groupStyles = css({ + display: 'flex', + flexDirection: 'row', + gap: spacing[200], + marginTop: spacing[200], +}); + +type ConnectionsFilterPopoverProps = PropsWithChildren<{ + open: boolean; + setOpen: (open: boolean) => void; + filter: ConnectionsFilter; + onFilterChange( + updater: (filter: ConnectionsFilter) => ConnectionsFilter + ): void; +}>; + +export default function ConnectionsFilterPopover({ + open, + setOpen, + filter, + onFilterChange, +}: ConnectionsFilterPopoverProps) { + const onExcludeInactiveChange = useCallback( + (excludeInactive: boolean) => { + onFilterChange((filter) => ({ + ...filter, + excludeInactive, + })); + }, + [onFilterChange] + ); + + const excludeInactiveId = useId('Sort by'); + + return ( + ( + <> + } + > + {/* TODO: Show a small blue circle when filter.excludeInactive is enabled */} + + + } + > + Filter connections + + {children} + + )} + > + Filter Options +
+ + +
+
+ ); +} diff --git a/packages/compass-sidebar/src/components/navigation-items-filter.tsx b/packages/compass-sidebar/src/components/navigation-items-filter.tsx index 2ea3348cc4c..2df22780a96 100644 --- a/packages/compass-sidebar/src/components/navigation-items-filter.tsx +++ b/packages/compass-sidebar/src/components/navigation-items-filter.tsx @@ -1,14 +1,7 @@ -import React, { useCallback } from 'react'; -import { - ConnectedPlugsIcon, - css, - DisconnectedPlugIcon, - IconButton, - spacing, - TextInput, - Tooltip, -} from '@mongodb-js/compass-components'; +import React, { useCallback, useState } from 'react'; +import { css, spacing, TextInput } from '@mongodb-js/compass-components'; import type { ConnectionsFilter } from './use-filtered-connections'; +import ConnectionsFilterPopover from './connections-filter-popover'; const filterContainerStyles = css({ display: 'flex', @@ -54,12 +47,7 @@ export default function NavigationItemsFilter({ [onFilterChange] ); - const toggleExcludeInactive = useCallback(() => { - onFilterChange((filter) => ({ - ...filter, - excludeInactive: !filter.excludeInactive, - })); - }, [onFilterChange]); + const [isPopoverOpen, setPopoverOpen] = useState(false); const onSubmit = useCallback((evt) => { evt.preventDefault(); @@ -78,30 +66,12 @@ export default function NavigationItemsFilter({ onChange={onChange} /> - - {filter.excludeInactive ? ( - - ) : ( - - )} - - } - > - {filter.excludeInactive - ? 'Showing active connections' - : 'Showing all connections'} - + ); } From b0aea1a6cdbd4ed5b0826c95d9925b81169b2bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 13 Nov 2024 11:50:21 +0100 Subject: [PATCH 03/12] Delete unused icons --- .../src/components/icons/connected-plugs.tsx | 46 ------------------- .../components/icons/disconnected-plug.tsx | 27 ----------- packages/compass-components/src/index.ts | 2 - 3 files changed, 75 deletions(-) delete mode 100644 packages/compass-components/src/components/icons/connected-plugs.tsx delete mode 100644 packages/compass-components/src/components/icons/disconnected-plug.tsx diff --git a/packages/compass-components/src/components/icons/connected-plugs.tsx b/packages/compass-components/src/components/icons/connected-plugs.tsx deleted file mode 100644 index a9e4912e384..00000000000 --- a/packages/compass-components/src/components/icons/connected-plugs.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { palette } from '@leafygreen-ui/palette'; -import React, { useMemo } from 'react'; - -import { useDarkMode } from '../../hooks/use-theme'; - -const ConnectedPlugsIcon: React.FunctionComponent = () => { - const darkMode = useDarkMode(); - - const fillColor = useMemo( - () => (darkMode ? palette.white : palette.black), - [darkMode] - ); - - const sparkColor = palette.green.dark1; - - return ( - - - - - - - - - ); -}; - -export { ConnectedPlugsIcon }; diff --git a/packages/compass-components/src/components/icons/disconnected-plug.tsx b/packages/compass-components/src/components/icons/disconnected-plug.tsx deleted file mode 100644 index efd40a711d3..00000000000 --- a/packages/compass-components/src/components/icons/disconnected-plug.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { palette } from '@leafygreen-ui/palette'; -import React, { useMemo } from 'react'; - -import { useDarkMode } from '../../hooks/use-theme'; - -const DisconnectedPlugIcon: React.FunctionComponent = () => { - const darkMode = useDarkMode(); - - const fillColor = useMemo( - () => (darkMode ? palette.white : palette.black), - [darkMode] - ); - - return ( - - - - ); -}; - -export { DisconnectedPlugIcon }; diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index 15e7af1afe8..3d80c9d7d2f 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -59,8 +59,6 @@ export { DocumentIcon } from './components/icons/document-icon'; export { FavoriteIcon } from './components/icons/favorite-icon'; export { ServerIcon } from './components/icons/server-icon'; export { NoSavedItemsIcon } from './components/icons/no-saved-items-icon'; -export { ConnectedPlugsIcon } from './components/icons/connected-plugs'; -export { DisconnectedPlugIcon } from './components/icons/disconnected-plug'; export { GuideCue as LGGuideCue } from '@leafygreen-ui/guide-cue'; export { Variant as BadgeVariant } from '@leafygreen-ui/badge'; export { Variant as BannerVariant } from '@leafygreen-ui/banner'; From 464e79d379c32924471c4a8db783c3421d744af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 13 Nov 2024 14:50:39 +0100 Subject: [PATCH 04/12] Add activated indicator --- .../components/connections-filter-popover.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/compass-sidebar/src/components/connections-filter-popover.tsx b/packages/compass-sidebar/src/components/connections-filter-popover.tsx index c4902adf19d..ddb714b59e7 100644 --- a/packages/compass-sidebar/src/components/connections-filter-popover.tsx +++ b/packages/compass-sidebar/src/components/connections-filter-popover.tsx @@ -7,6 +7,7 @@ import { InteractivePopover, Label, Overline, + palette, spacing, Toggle, Tooltip, @@ -23,9 +24,17 @@ const containerStyles = css({ }); const closeButtonStyles = css({ + // An alternative to this is to pass hideCloseButton to InteractivePopover, + // but that throws an error when the popover is opened display: 'none', }); +const activatedIndicatorStyles = css({ + position: 'absolute', + top: spacing[50], + right: spacing[50], +}); + const groupStyles = css({ display: 'flex', flexDirection: 'row', @@ -60,6 +69,9 @@ export default function ConnectionsFilterPopover({ const excludeInactiveId = useId('Sort by'); + // Add future filters to the boolean below + const isActivated = filter.excludeInactive; + return ( {/* TODO: Show a small blue circle when filter.excludeInactive is enabled */} + {isActivated && ( + + + + )} } > From 6d19bbe31783aa232ccfcc24c7f4d84444b2098f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 13 Nov 2024 15:32:25 +0100 Subject: [PATCH 05/12] Blur trigger when closing popover --- .../src/components/interactive-popover.tsx | 10 ++++++++-- .../src/components/connections-filter-popover.tsx | 13 +++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/compass-components/src/components/interactive-popover.tsx b/packages/compass-components/src/components/interactive-popover.tsx index e7ed4aa442c..a0faff3c376 100644 --- a/packages/compass-components/src/components/interactive-popover.tsx +++ b/packages/compass-components/src/components/interactive-popover.tsx @@ -60,6 +60,7 @@ type InteractivePopoverProps = { containedElements?: string[]; containerClassName?: string; closeButtonClassName?: string; + blurTriggerOnClose?: boolean; } & Pick< React.ComponentProps, 'align' | 'justify' | 'spacing' | 'popoverZIndex' @@ -80,6 +81,7 @@ function InteractivePopover({ popoverZIndex, containerClassName, closeButtonClassName, + blurTriggerOnClose = false, }: InteractivePopoverProps): React.ReactElement { const darkMode = useDarkMode(); const triggerRef = useRef(null); @@ -91,9 +93,13 @@ function InteractivePopover({ // Return focus to the trigger when the popover is hidden. setTimeout(() => { - triggerRef.current?.focus(); + if (blurTriggerOnClose) { + triggerRef.current?.blur(); + } else { + triggerRef.current?.focus(); + } }); - }, [setOpen]); + }, [setOpen, blurTriggerOnClose]); const onClickTrigger = useCallback( (event) => { diff --git a/packages/compass-sidebar/src/components/connections-filter-popover.tsx b/packages/compass-sidebar/src/components/connections-filter-popover.tsx index ddb714b59e7..ff5a6f7ef0c 100644 --- a/packages/compass-sidebar/src/components/connections-filter-popover.tsx +++ b/packages/compass-sidebar/src/components/connections-filter-popover.tsx @@ -67,7 +67,8 @@ export default function ConnectionsFilterPopover({ [onFilterChange] ); - const excludeInactiveId = useId('Sort by'); + const excludeInactiveToggleId = useId('exclude-inactive-toggle'); + const excludeInactiveLabelId = useId('exclude-inactive-label'); // Add future filters to the boolean below const isActivated = filter.excludeInactive; @@ -76,13 +77,14 @@ export default function ConnectionsFilterPopover({ ( <> Filter Options
- +
); From 5e901dd484dfc62758276944b8986c4d611d652c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 13 Nov 2024 15:32:35 +0100 Subject: [PATCH 06/12] Fix failing tests --- .../multiple-connections/sidebar.spec.tsx | 11 +- .../use-filtered-connections.spec.ts | 132 ++++++++++-------- 2 files changed, 80 insertions(+), 63 deletions(-) diff --git a/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx b/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx index 7093ae89944..fa37ac4ec5f 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx @@ -363,16 +363,13 @@ describe('Multiple Connections Sidebar Component', function () { const favoriteConnectionId = savedFavoriteConnection.id; const recentConnectionId = savedRecentConnection.id; - const activeConnectionsToggleButton = screen.getByLabelText( - 'Showing all connections' - ); - expect(screen.queryByTestId(favoriteConnectionId)).to.be.visible; expect(screen.queryByTestId(recentConnectionId)).to.be.visible; - userEvent.click(activeConnectionsToggleButton); - expect(activeConnectionsToggleButton.ariaLabel).equals( - 'Showing active connections' + userEvent.click(screen.getByLabelText('Filter connections')); + + userEvent.click( + screen.getByLabelText('Show only active connections') ); expect(screen.queryByTestId(favoriteConnectionId)).to.be.null; diff --git a/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts b/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts index 5d93c0b65e1..8a9aa3e71e3 100644 --- a/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts +++ b/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts @@ -137,10 +137,9 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -153,10 +152,9 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -176,10 +174,9 @@ describe('useFilteredConnections', function () { { initialProps: { connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: true }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: true, }, } ); @@ -191,10 +188,9 @@ describe('useFilteredConnections', function () { rerender({ connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }); expect(result.current.filtered).to.be.undefined; @@ -206,10 +202,9 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -238,10 +233,9 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -291,10 +285,9 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -328,10 +321,9 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -375,10 +367,9 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -409,10 +400,9 @@ describe('useFilteredConnections', function () { { initialProps: { connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, } ); @@ -444,10 +434,9 @@ describe('useFilteredConnections', function () { ]; rerender({ connections: newConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }); // should remove the expanded state of connection 2 await waitFor(() => { @@ -461,10 +450,9 @@ describe('useFilteredConnections', function () { // now pretend again that connection2 is connected rerender({ connections: mockSidebarConnections, - filterRegex: null, + filter: { regex: null, excludeInactive: false }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }); await waitFor(() => { expect(result.current.expanded).to.deep.equal({ @@ -484,10 +472,12 @@ describe('useFilteredConnections', function () { { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('_connection', 'i'), // match everything basically + filter: { + regex: new RegExp('_connection', 'i'), // match everything basically + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, } ); @@ -500,10 +490,12 @@ describe('useFilteredConnections', function () { rerender({ connections: mockSidebarConnections, - filterRegex: new RegExp('disconnected_connection', 'i'), // match disconnected one + filter: { + regex: new RegExp('disconnected_connection', 'i'), // match disconnected one + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }); await waitFor(() => { expect(result.current.filtered).to.be.deep.equal([ @@ -516,10 +508,12 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('db_ready_1_1', 'i'), // match first database basically + filter: { + regex: new RegExp('db_ready_1_1', 'i'), // match first database basically + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -557,10 +551,12 @@ describe('useFilteredConnections', function () { ], } as SidebarConnectedConnection, ], - filterRegex: new RegExp('Matching', 'i'), // this matches connection as well as database + filter: { + regex: new RegExp('Matching', 'i'), // this matches connection as well as database + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -575,10 +571,12 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('coll_ready_2_1', 'i'), // match second db's collection + filter: { + regex: new RegExp('coll_ready_2_1', 'i'), // match second db's collection + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -604,10 +602,12 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('ready_2_1', 'i'), // this matches 1 database and 1 collection + filter: { + regex: new RegExp('ready_2_1', 'i'), // this matches 1 database and 1 collection + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -623,10 +623,12 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('coll_ready_1_1', 'i'), + filter: { + regex: new RegExp('coll_ready_1_1', 'i'), + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -648,10 +650,12 @@ describe('useFilteredConnections', function () { { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('connection_1'), + filter: { + regex: new RegExp('connection_1'), + excludeInactive: true, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: true, }, } ); @@ -662,10 +666,12 @@ describe('useFilteredConnections', function () { rerender({ connections: mockSidebarConnections, - filterRegex: new RegExp('connection_1'), + filter: { + regex: new RegExp('connection_1'), + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }); expect(result.current.filtered).to.be.deep.equal([ @@ -682,10 +688,12 @@ describe('useFilteredConnections', function () { { initialProps: { connections: mockSidebarConnections, - filterRegex: null as RegExp | null, + filter: { + regex: null as RegExp | null, + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, } ); @@ -701,10 +709,12 @@ describe('useFilteredConnections', function () { rerender({ connections: mockSidebarConnections, - filterRegex: new RegExp('coll_ready_1_1', 'i'), + filter: { + regex: new RegExp('coll_ready_1_1', 'i'), + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }); await waitFor(() => { expect(result.current.expanded).to.deep.equal({ @@ -725,10 +735,12 @@ describe('useFilteredConnections', function () { { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('coll_ready_1_1', 'i') as RegExp | null, + filter: { + regex: new RegExp('coll_ready_1_1', 'i') as RegExp | null, + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, } ); @@ -744,10 +756,12 @@ describe('useFilteredConnections', function () { rerender({ connections: mockSidebarConnections, - filterRegex: null, + filter: { + regex: null, + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }); await waitFor(() => { expect(result.current.expanded).to.deep.equal({ @@ -766,10 +780,12 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('coll_ready_1_1', 'i'), + filter: { + regex: new RegExp('coll_ready_1_1', 'i'), + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -799,10 +815,12 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('coll_ready_1_1', 'i'), + filter: { + regex: new RegExp('coll_ready_1_1', 'i'), + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); @@ -838,10 +856,12 @@ describe('useFilteredConnections', function () { const { result } = renderHookWithContext(useFilteredConnections, { initialProps: { connections: mockSidebarConnections, - filterRegex: new RegExp('coll_ready_1_1', 'i'), + filter: { + regex: new RegExp('coll_ready_1_1', 'i'), + excludeInactive: false, + }, fetchAllCollections: fetchAllCollectionsStub, onDatabaseExpand: onDatabaseExpandStub, - excludeInactive: false, }, }); From 207bbc17a1fb440e47ed5630440429be34ff8eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 13 Nov 2024 15:33:27 +0100 Subject: [PATCH 07/12] Avoid className prop on NavigationItemsFilter --- .../connections-navigation.tsx | 5 ---- .../components/navigation-items-filter.tsx | 29 ++++++++++--------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/compass-sidebar/src/components/multiple-connections/connections-navigation.tsx b/packages/compass-sidebar/src/components/multiple-connections/connections-navigation.tsx index b98833e27d2..dac88d84deb 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/connections-navigation.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/connections-navigation.tsx @@ -83,10 +83,6 @@ const connectionCountStyles = css({ marginLeft: spacing[100], }); -const searchFormStyles = css({ - flexGrow: 1, -}); - const noDeploymentStyles = css({ paddingLeft: spacing[400], paddingRight: spacing[400], @@ -507,7 +503,6 @@ const ConnectionsNavigation: React.FC = ({ {connections.length > 0 && ( <> ConnectionsFilter ): void; - className?: string; }): React.ReactElement { const onChange = useCallback>( (event) => { @@ -55,23 +57,22 @@ export default function NavigationItemsFilter({ }, []); return ( -
-
- - +
+ -
+ ); } From efc191c6ff74c463cf87742e32075db81a3690aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 13 Nov 2024 17:59:13 +0100 Subject: [PATCH 08/12] fixup! Refactor to a popover Pass hideCloseButton instead of using display none --- .../src/components/connections-filter-popover.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/compass-sidebar/src/components/connections-filter-popover.tsx b/packages/compass-sidebar/src/components/connections-filter-popover.tsx index ff5a6f7ef0c..011e6d8d03a 100644 --- a/packages/compass-sidebar/src/components/connections-filter-popover.tsx +++ b/packages/compass-sidebar/src/components/connections-filter-popover.tsx @@ -23,12 +23,6 @@ const containerStyles = css({ minWidth: 270, }); -const closeButtonStyles = css({ - // An alternative to this is to pass hideCloseButton to InteractivePopover, - // but that throws an error when the popover is opened - display: 'none', -}); - const activatedIndicatorStyles = css({ position: 'absolute', top: spacing[50], @@ -78,8 +72,8 @@ export default function ConnectionsFilterPopover({ open={open} setOpen={setOpen} blurTriggerOnClose - closeButtonClassName={closeButtonStyles} containerClassName={containerStyles} + hideCloseButton trigger={({ onClick, children, ref }) => ( <> Date: Wed, 13 Nov 2024 18:00:24 +0100 Subject: [PATCH 09/12] fixup! Blur trigger when closing popover Fix generating unique ids --- .../src/components/connections-filter-popover.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/compass-sidebar/src/components/connections-filter-popover.tsx b/packages/compass-sidebar/src/components/connections-filter-popover.tsx index 011e6d8d03a..3e67555acba 100644 --- a/packages/compass-sidebar/src/components/connections-filter-popover.tsx +++ b/packages/compass-sidebar/src/components/connections-filter-popover.tsx @@ -61,8 +61,8 @@ export default function ConnectionsFilterPopover({ [onFilterChange] ); - const excludeInactiveToggleId = useId('exclude-inactive-toggle'); - const excludeInactiveLabelId = useId('exclude-inactive-label'); + const excludeInactiveToggleId = useId(); + const excludeInactiveLabelId = useId(); // Add future filters to the boolean below const isActivated = filter.excludeInactive; From 03f93b8b6617837ddabf0cc74de6bbf24cf4b6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 13 Nov 2024 18:00:42 +0100 Subject: [PATCH 10/12] Remove comments --- .../src/components/connections-filter-popover.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/compass-sidebar/src/components/connections-filter-popover.tsx b/packages/compass-sidebar/src/components/connections-filter-popover.tsx index 3e67555acba..fa435d3d4f5 100644 --- a/packages/compass-sidebar/src/components/connections-filter-popover.tsx +++ b/packages/compass-sidebar/src/components/connections-filter-popover.tsx @@ -78,7 +78,6 @@ export default function ConnectionsFilterPopover({ <> } > - {/* TODO: Show a small blue circle when filter.excludeInactive is enabled */} {isActivated && ( Date: Thu, 14 Nov 2024 09:52:23 +0100 Subject: [PATCH 11/12] Revert "Blur trigger when closing popover" This reverts part of commit 6d19bbe31783aa232ccfcc24c7f4d84444b2098f. --- .../src/components/interactive-popover.tsx | 10 ++-------- .../src/components/connections-filter-popover.tsx | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/compass-components/src/components/interactive-popover.tsx b/packages/compass-components/src/components/interactive-popover.tsx index a0faff3c376..e7ed4aa442c 100644 --- a/packages/compass-components/src/components/interactive-popover.tsx +++ b/packages/compass-components/src/components/interactive-popover.tsx @@ -60,7 +60,6 @@ type InteractivePopoverProps = { containedElements?: string[]; containerClassName?: string; closeButtonClassName?: string; - blurTriggerOnClose?: boolean; } & Pick< React.ComponentProps, 'align' | 'justify' | 'spacing' | 'popoverZIndex' @@ -81,7 +80,6 @@ function InteractivePopover({ popoverZIndex, containerClassName, closeButtonClassName, - blurTriggerOnClose = false, }: InteractivePopoverProps): React.ReactElement { const darkMode = useDarkMode(); const triggerRef = useRef(null); @@ -93,13 +91,9 @@ function InteractivePopover({ // Return focus to the trigger when the popover is hidden. setTimeout(() => { - if (blurTriggerOnClose) { - triggerRef.current?.blur(); - } else { - triggerRef.current?.focus(); - } + triggerRef.current?.focus(); }); - }, [setOpen, blurTriggerOnClose]); + }, [setOpen]); const onClickTrigger = useCallback( (event) => { diff --git a/packages/compass-sidebar/src/components/connections-filter-popover.tsx b/packages/compass-sidebar/src/components/connections-filter-popover.tsx index fa435d3d4f5..404c6815041 100644 --- a/packages/compass-sidebar/src/components/connections-filter-popover.tsx +++ b/packages/compass-sidebar/src/components/connections-filter-popover.tsx @@ -71,7 +71,6 @@ export default function ConnectionsFilterPopover({ ( From 65beec5a6f1ae5fade6ca7fd7dd0f99709fed20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Thu, 14 Nov 2024 10:21:38 +0100 Subject: [PATCH 12/12] Control tooltip to avoid it showing when closing popover --- .../components/connections-filter-popover.tsx | 123 ++++++++++-------- 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/packages/compass-sidebar/src/components/connections-filter-popover.tsx b/packages/compass-sidebar/src/components/connections-filter-popover.tsx index 404c6815041..4cf936dadf3 100644 --- a/packages/compass-sidebar/src/components/connections-filter-popover.tsx +++ b/packages/compass-sidebar/src/components/connections-filter-popover.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, type PropsWithChildren } from 'react'; +import React, { useCallback, useState, type PropsWithChildren } from 'react'; import { css, @@ -67,58 +67,75 @@ export default function ConnectionsFilterPopover({ // Add future filters to the boolean below const isActivated = filter.excludeInactive; + // Manually handling the tooltip state instead of supplying a trigger + // we do this to avoid the tooltip from rendering when the popover is open + // and when the IconButton regains focus as the + const [isTooltipOpen, setTooltipOpen] = useState(false); + const handleButtonMouseEnter = useCallback( + () => setTooltipOpen(true), + [setTooltipOpen] + ); + const handleButtonMouseLeave = useCallback( + () => setTooltipOpen(false), + [setTooltipOpen] + ); + return ( - ( - <> - } - > - - {isActivated && ( - - - - )} - - } - > - Filter connections - - {children} - - )} - > - Filter Options -
- - -
-
+ <> + + Filter connections + + ( + <> + } + > + + {isActivated && ( + + + + )} + + {children} + + )} + > + Filter Options +
+ + +
+
+ ); }