-
Notifications
You must be signed in to change notification settings - Fork 188
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
Changes from 10 commits
754ab49
4fd49fc
b0aea1a
464e79d
6d19bbe
5e901dd
207bbc1
efc191c
d02f274
03f93b8
b8f783f
65beec5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import React, { useCallback, 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; | ||
|
||
return ( | ||
<InteractivePopover | ||
open={open} | ||
setOpen={setOpen} | ||
blurTriggerOnClose | ||
containerClassName={containerStyles} | ||
hideCloseButton | ||
trigger={({ onClick, children, ref }) => ( | ||
<> | ||
<Tooltip | ||
align="right" | ||
trigger={ | ||
<IconButton | ||
onClick={onClick} | ||
active={open} | ||
aria-label="Filter connections" | ||
ref={ref as React.Ref<unknown>} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest, I didn't fully understand the need for this. It seems the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. InteractivePopover was initially built to be used with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, that could probably be fixed with updates to the |
||
> | ||
<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> | ||
} | ||
> | ||
Filter connections | ||
</Tooltip> | ||
{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 |
---|---|---|
|
@@ -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,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], | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've combined the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -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, | ||
|
@@ -270,10 +254,9 @@ const ConnectionsNavigation: React.FC<ConnectionsNavigationProps> = ({ | |
onDatabaseToggle, | ||
} = useFilteredConnections({ | ||
connections, | ||
filterRegex, | ||
filter, | ||
fetchAllCollections, | ||
onDatabaseExpand, | ||
excludeInactive, | ||
}); | ||
|
||
const connectionListTitleActions = | ||
|
@@ -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} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a common accessible behavior to return focus back to the trigger, especially helpful if you're navigating with a keyboard, all leafygreen Menus are doing that and we are following their behavior here with our InteractivePopover. Why are we breaking the pattern here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this because bringing back the focus makes the tooltip show even if it's not hoved 🤔 Perhaps I can return focus to something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For what it's worth, it seems the "tab position" is remembered. I.e. when tabbing after closing the popover you can continue to the connections tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your focus is on body though, you don't get notificed by voice over that you're back on button. If we want to really address this, we probably should make sure that the button has a proper (aria-)label and shows tooltip only on hover where it makes sense to show it, not on keyboard focus, but this probably requires figuring this out with leafygreen team. You can probably circumvent this by using an extra sibling element as trigger and not the IconButton itself, this should also work. Or we can just ignore it, we're compromising on accessibility in a lot of other places too already 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I would prefer though if possible is not to add a prop to a shared component that explicitly breaks its accessibility behavior. If we can hack around this outside of it to make this tooltip not show up in this exact place where we need this for some reason, that would be nice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but I agree we should avoid regressing further on this 👍
I reverted the change to add a
blurTriggerOnClose
prop and instead usedTooltip
as a controlled component. I also updated the screen capture in the description above ☝️ Thanks a lot for the explanations and the comment in the first place 👍