Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sidebar): connections filter popover COMPASS-8503 #6486

Merged
merged 12 commits into from
Nov 14, 2024

This file was deleted.

This file was deleted.

2 changes: 0 additions & 2 deletions packages/compass-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
141 changes: 141 additions & 0 deletions packages/compass-sidebar/src/components/connections-filter-popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { useCallback, useState, type PropsWithChildren } from 'react';

import {
css,
Icon,
IconButton,
InteractivePopover,
Label,
Overline,
palette,
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 activatedIndicatorStyles = css({
position: 'absolute',
top: spacing[50],
right: spacing[50],
});

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 excludeInactiveToggleId = useId();
const excludeInactiveLabelId = useId();

// Add future filters to the boolean below
const isActivated = filter.excludeInactive;

// Manually handling the tooltip state instead of supplying a trigger
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

// we do this to avoid the tooltip from rendering when the popover is open
// and when the IconButton regains focus as the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already merged, but your comment ends mid-sentence :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks: #6502

const [isTooltipOpen, setTooltipOpen] = useState(false);
const handleButtonMouseEnter = useCallback(
() => setTooltipOpen(true),
[setTooltipOpen]
);
const handleButtonMouseLeave = useCallback(
() => setTooltipOpen(false),
[setTooltipOpen]
);

return (
<>
<Tooltip
align="right"
open={isTooltipOpen && !open}
setOpen={setTooltipOpen}
>
Filter connections
</Tooltip>
<InteractivePopover
open={open}
setOpen={setOpen}
containerClassName={containerStyles}
hideCloseButton
trigger={({ onClick, children, ref }) => (
<>
<IconButton
onClick={onClick}
onMouseEnter={handleButtonMouseEnter}
onMouseLeave={handleButtonMouseLeave}
active={open}
aria-label="Filter connections"
ref={ref as React.Ref<unknown>}
>
<Icon glyph="Filter" />
{isActivated && (
<svg
className={activatedIndicatorStyles}
width="6"
height="6"
viewBox="0 0 6 6"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="3" cy="3" r="3" fill={palette.blue.base} />
</svg>
)}
</IconButton>
{children}
</>
)}
>
<Overline>Filter Options</Overline>
<div className={groupStyles}>
<Toggle
id={excludeInactiveToggleId}
aria-labelledby={excludeInactiveLabelId}
checked={filter.excludeInactive}
onChange={onExcludeInactiveChange}
size="small"
/>
<Label htmlFor={excludeInactiveToggleId} id={excludeInactiveLabelId}>
Show only active connections
</Label>
</div>
</InteractivePopover>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -84,19 +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,
});

const noDeploymentStyles = css({
paddingLeft: spacing[400],
paddingRight: spacing[400],
Expand All @@ -123,10 +109,10 @@ type ConnectionListTitleActions =
type ConnectionsNavigationComponentProps = {
connectionsWithStatus: ReturnType<typeof useConnectionsWithStatus>;
activeWorkspace: WorkspaceTab | null;
filterRegex: RegExp | null;
excludeInactive: boolean;
onFilterChange(regex: RegExp | null): void;
onToggleExcludeInactive(): void;
filter: ConnectionsFilter;
Copy link
Contributor Author

@kraenhansen kraenhansen Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've combined the filterRegex and excludeInactive into a single filter prop as adding the pop-over suggests we might be adding more parameters for filtering in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I believe that's the plan with the popover.

onFilterChange(
updater: (filter: ConnectionsFilter) => ConnectionsFilter
): void;
onConnect(info: ConnectionInfo): void;
onNewConnection(): void;
onEditConnection(info: ConnectionInfo): void;
Expand Down Expand Up @@ -167,13 +153,11 @@ type ConnectionsNavigationProps = ConnectionsNavigationComponentProps &
const ConnectionsNavigation: React.FC<ConnectionsNavigationProps> = ({
connectionsWithStatus,
activeWorkspace,
filterRegex,
excludeInactive,
filter,
instances,
databases,
isPerformanceTabSupported,
onFilterChange,
onToggleExcludeInactive,
onConnect,
onNewConnection,
onEditConnection,
Expand Down Expand Up @@ -270,10 +254,9 @@ const ConnectionsNavigation: React.FC<ConnectionsNavigationProps> = ({
onDatabaseToggle,
} = useFilteredConnections({
connections,
filterRegex,
filter,
fetchAllCollections,
onDatabaseExpand,
excludeInactive,
});

const connectionListTitleActions =
Expand Down Expand Up @@ -519,37 +502,11 @@ const ConnectionsNavigation: React.FC<ConnectionsNavigationProps> = ({
</div>
{connections.length > 0 && (
<>
<div className={filterContainerStyles}>
<NavigationItemsFilter
className={searchFormStyles}
placeholder="Search connections"
onFilterChange={onFilterChange}
/>
<Tooltip
justify="middle"
trigger={
<IconButton
onClick={onToggleExcludeInactive}
active={excludeInactive}
aria-label={
excludeInactive
? 'Showing active connections'
: 'Showing all connections'
}
>
{excludeInactive ? (
<ConnectedPlugsIcon />
) : (
<DisconnectedPlugIcon />
)}
</IconButton>
}
>
{excludeInactive
? 'Showing active connections'
: 'Showing all connections'}
</Tooltip>
</div>
<NavigationItemsFilter
placeholder="Search connections"
filter={filter}
onFilterChange={onFilterChange}
/>
<ConnectionsNavigationTree
connections={filtered || connections}
activeWorkspace={activeWorkspace}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading