1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-27 01:19:00 +02:00

chore: command bar refactor of search result items for consistent styling and icons (#7483)

This commit is contained in:
David Leek 2024-06-28 12:40:44 +02:00 committed by GitHub
parent d01aba955a
commit bdce76e84a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 270 additions and 277 deletions

View File

@ -14,7 +14,6 @@ import { useKeyboardShortcut } from 'hooks/useKeyboardShortcut';
import { SEARCH_INPUT } from 'utils/testIds'; import { SEARCH_INPUT } from 'utils/testIds';
import { useOnClickOutside } from 'hooks/useOnClickOutside'; import { useOnClickOutside } from 'hooks/useOnClickOutside';
import { useOnBlur } from 'hooks/useOnBlur'; import { useOnBlur } from 'hooks/useOnBlur';
import { CommandRecent } from './CommandRecent';
import { useRecentlyVisited } from 'hooks/useRecentlyVisited'; import { useRecentlyVisited } from 'hooks/useRecentlyVisited';
import { import {
CommandResultGroup, CommandResultGroup,
@ -26,6 +25,8 @@ import { useAsyncDebounce } from 'react-table';
import useProjects from 'hooks/api/getters/useProjects/useProjects'; import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { CommandFeatures } from './CommandFeatures'; import { CommandFeatures } from './CommandFeatures';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { CommandRecent } from './CommandRecent';
import { CommandPages } from './CommandPages';
export const CommandResultsPaper = styled(Paper)(({ theme }) => ({ export const CommandResultsPaper = styled(Paper)(({ theme }) => ({
position: 'absolute', position: 'absolute',
@ -259,11 +260,7 @@ export const CommandBar = () => {
icon={'flag'} icon={'flag'}
items={searchedProjects} items={searchedProjects}
/> />
<CommandResultGroup <CommandPages items={searchedPages} />
groupName={'Pages'}
icon={'flag'}
items={searchedPages}
/>
</CommandResultsPaper> </CommandResultsPaper>
} }
elseShow={ elseShow={

View File

@ -1,49 +1,16 @@
import { import {
List, CommandResultGroup,
ListItemButton, listItemButtonStyle,
ListItemIcon, StyledButtonTypography,
ListItemText, StyledListItemIcon,
styled, StyledListItemText,
Typography, } from './RecentlyVisited/CommandResultGroup';
} from '@mui/material'; import { ListItemButton } from '@mui/material';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { IconRenderer } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer'; import { IconRenderer } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
import type { Theme } from '@mui/material/styles/createTheme';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import type { JSX } from 'react'; import type { JSX } from 'react';
const listItemButtonStyle = (theme: Theme) => ({
border: `1px solid transparent`,
borderLeft: `${theme.spacing(0.5)} solid transparent`,
'&:hover': {
border: `1px solid ${theme.palette.primary.main}`,
borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
},
});
const StyledContainer = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(3),
}));
const StyledButtonTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallerBody,
}));
const StyledTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
padding: theme.spacing(0, 2.5),
}));
const StyledListItemIcon = styled(ListItemIcon)(({ theme }) => ({
minWidth: theme.spacing(4),
margin: theme.spacing(0.25, 0),
}));
const StyledListItemText = styled(ListItemText)(({ theme }) => ({
margin: 0,
fontSize: theme.fontSizes.smallBody,
}));
interface IPageSuggestionItem { interface IPageSuggestionItem {
icon: JSX.Element; icon: JSX.Element;
name: string; name: string;
@ -93,9 +60,7 @@ export const CommandPageSuggestions = ({
}); });
}; };
return ( return (
<StyledContainer> <CommandResultGroup icon='pages' groupName='Pages'>
<StyledTypography color='textSecondary'>Pages</StyledTypography>
<List>
{pageItems.map((item, index) => ( {pageItems.map((item, index) => (
<ListItemButton <ListItemButton
key={`recently-visited-${index}`} key={`recently-visited-${index}`}
@ -109,7 +74,6 @@ export const CommandPageSuggestions = ({
> >
<StyledListItemIcon <StyledListItemIcon
sx={(theme) => ({ sx={(theme) => ({
color: theme.palette.primary.main,
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
minWidth: theme.spacing(0.5), minWidth: theme.spacing(0.5),
margin: theme.spacing(0, 1, 0, 0), margin: theme.spacing(0, 1, 0, 0),
@ -124,7 +88,6 @@ export const CommandPageSuggestions = ({
</StyledListItemText> </StyledListItemText>
</ListItemButton> </ListItemButton>
))} ))}
</List> </CommandResultGroup>
</StyledContainer>
); );
}; };

View File

@ -0,0 +1,63 @@
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { Link } from 'react-router-dom';
import {
CommandResultGroup,
StyledButtonTypography,
StyledListItemIcon,
StyledListItemText,
listItemButtonStyle,
type CommandResultGroupItem,
} from './RecentlyVisited/CommandResultGroup';
import { ListItemButton } from '@mui/material';
import { IconRenderer } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
export const CommandPages = ({
items,
}: {
items: CommandResultGroupItem[];
}) => {
const { trackEvent } = usePlausibleTracker();
const groupName = 'Pages';
const onClick = (item: CommandResultGroupItem) => {
trackEvent('command-bar', {
props: {
eventType: `click`,
source: 'search',
eventTarget: groupName,
...(groupName === 'Pages' && { pageType: item.name }),
},
});
};
return (
<CommandResultGroup groupName={'Pages'} icon={'default'}>
{items.map((item, index) => (
<ListItemButton
key={`command-result-group-pages-${index}`}
dense={true}
component={Link}
to={item.link}
onClick={() => {
onClick(item);
}}
sx={listItemButtonStyle}
>
<StyledListItemIcon
sx={(theme) => ({
fontSize: theme.fontSizes.smallBody,
minWidth: theme.spacing(0.5),
margin: theme.spacing(0, 1, 0, 0),
})}
>
<IconRenderer path={item.link} />
</StyledListItemIcon>
<StyledListItemText>
<StyledButtonTypography color='textPrimary'>
{item.name}
</StyledButtonTypography>
</StyledListItemText>
</ListItemButton>
))}
</CommandResultGroup>
);
};

View File

@ -1,60 +1,11 @@
import { import {
Icon, CommandResultGroup,
List, RecentlyVisitedFeatureButton,
ListItemButton, RecentlyVisitedPathButton,
ListItemIcon, RecentlyVisitedProjectButton,
ListItemText, } from './RecentlyVisited/CommandResultGroup';
styled, import { List } from '@mui/material';
Typography,
} from '@mui/material';
import { Link } from 'react-router-dom';
import {
IconRenderer,
StyledProjectIcon,
} from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
import type { LastViewedPage } from 'hooks/useRecentlyVisited'; import type { LastViewedPage } from 'hooks/useRecentlyVisited';
import type { Theme } from '@mui/material/styles/createTheme';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
const listItemButtonStyle = (theme: Theme) => ({
border: `1px solid transparent`,
borderLeft: `${theme.spacing(0.5)} solid transparent`,
'&:hover': {
border: `1px solid ${theme.palette.primary.main}`,
borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
},
});
const StyledContainer = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(3),
}));
const StyledButtonTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallerBody,
}));
const ColoredStyledProjectIcon = styled(StyledProjectIcon)(({ theme }) => ({
fill: theme.palette.primary.main,
stroke: theme.palette.primary.main,
}));
const StyledTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
padding: theme.spacing(0, 2.5),
}));
const StyledListItemIcon = styled(ListItemIcon)(({ theme }) => ({
minWidth: theme.spacing(0.5),
margin: theme.spacing(0, 1, 0, 0),
color: theme.palette.primary.main,
}));
const StyledListItemText = styled(ListItemText)(({ theme }) => ({
margin: 0,
fontSize: theme.fontSizes.smallBody,
}));
const toListItemButton = ( const toListItemButton = (
item: LastViewedPage, item: LastViewedPage,
@ -86,127 +37,6 @@ const toListItemButton = (
); );
}; };
const RecentlyVisitedFeatureButton = ({
key,
projectId,
featureId,
}: { key: string; projectId: string; featureId: string }) => {
const { trackEvent } = usePlausibleTracker();
const onClick = () => {
trackEvent('command-bar', {
props: {
eventType: `click`,
source: 'recently-visited',
eventTarget: 'Flags',
},
});
};
return (
<ListItemButton
key={key}
dense={true}
component={Link}
onClick={onClick}
to={`/projects/${projectId}/features/${featureId}`}
sx={listItemButtonStyle}
>
<StyledListItemIcon>
<Icon>{'flag'}</Icon>
</StyledListItemIcon>
<StyledListItemText>
<StyledButtonTypography color='textPrimary'>
{featureId}
</StyledButtonTypography>
</StyledListItemText>
</ListItemButton>
);
};
const RecentlyVisitedPathButton = ({
path,
key,
name,
}: { path: string; key: string; name: string }) => {
const { trackEvent } = usePlausibleTracker();
const onClick = () => {
trackEvent('command-bar', {
props: {
eventType: `click`,
source: 'recently-visited',
eventTarget: 'Pages',
pageType: name,
},
});
};
return (
<ListItemButton
key={key}
dense={true}
component={Link}
to={path}
onClick={onClick}
sx={listItemButtonStyle}
>
<StyledListItemIcon
sx={(theme) => ({ color: theme.palette.primary.main })}
>
<ConditionallyRender
condition={path === '/projects'}
show={<ColoredStyledProjectIcon />}
elseShow={<IconRenderer path={path} />}
/>
</StyledListItemIcon>
<StyledListItemText>
<StyledButtonTypography color='textPrimary'>
{name}
</StyledButtonTypography>
</StyledListItemText>
</ListItemButton>
);
};
const RecentlyVisitedProjectButton = ({
projectId,
key,
}: { projectId: string; key: string }) => {
const { trackEvent } = usePlausibleTracker();
const { project, loading } = useProjectOverview(projectId);
const projectDeleted = !project.name && !loading;
const onClick = () => {
trackEvent('command-bar', {
props: {
eventType: `click`,
source: 'recently-visited',
eventTarget: 'Projects',
},
});
};
if (projectDeleted) return null;
return (
<ListItemButton
key={key}
dense={true}
component={Link}
to={`/projects/${projectId}`}
onClick={onClick}
sx={listItemButtonStyle}
>
<StyledListItemIcon>
<ColoredStyledProjectIcon />
</StyledListItemIcon>
<StyledListItemText>
<StyledButtonTypography color='textPrimary'>
{project.name}
</StyledButtonTypography>
</StyledListItemText>
</ListItemButton>
);
};
export const CommandRecent = ({ export const CommandRecent = ({
lastVisited, lastVisited,
routes, routes,
@ -218,11 +48,8 @@ export const CommandRecent = ({
toListItemButton(item, routes, index), toListItemButton(item, routes, index),
); );
return ( return (
<StyledContainer> <CommandResultGroup icon='default' groupName='Quick suggestions'>
<StyledTypography color='textSecondary'>
Recently visited
</StyledTypography>
<List>{buttons}</List> <List>{buttons}</List>
</StyledContainer> </CommandResultGroup>
); );
}; };

View File

@ -10,30 +10,43 @@ import {
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import type { Theme } from '@mui/material/styles/createTheme'; import type { Theme } from '@mui/material/styles/createTheme';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StyledProjectIcon } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer'; import {
IconRenderer,
StyledProjectIcon,
} from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver'; import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
const listItemButtonStyle = (theme: Theme) => ({ export const listItemButtonStyle = (theme: Theme) => ({
borderRadius: theme.spacing(0.5), border: `1px solid transparent`,
borderLeft: `${theme.spacing(0.5)} solid transparent`, borderLeft: `${theme.spacing(0.5)} solid transparent`,
'&.Mui-selected': { '&:hover, &:focus': {
border: `1px solid ${theme.palette.primary.main}`,
borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`, borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
}, },
}); });
const StyledContainer = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(3),
}));
const StyledTypography = styled(Typography)(({ theme }) => ({ export const StyledTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.bodySize, fontSize: theme.fontSizes.bodySize,
padding: theme.spacing(0, 3), padding: theme.spacing(0, 2.5),
})); }));
const StyledListItemIcon = styled(ListItemIcon)(({ theme }) => ({ export const StyledListItemIcon = styled(ListItemIcon)(({ theme }) => ({
minWidth: theme.spacing(4), minWidth: theme.spacing(0.5),
margin: theme.spacing(0.25, 0), margin: theme.spacing(0, 1, 0, 0),
})); }));
const StyledListItemText = styled(ListItemText)(({ theme }) => ({ export const StyledListItemText = styled(ListItemText)(({ theme }) => ({
margin: 0, margin: 0,
fontSize: theme.fontSizes.bodySize,
}));
export const StyledButtonTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.bodySize,
})); }));
export interface CommandResultGroupItem { export interface CommandResultGroupItem {
@ -42,24 +55,150 @@ export interface CommandResultGroupItem {
description?: string | null; description?: string | null;
} }
export const RecentlyVisitedPathButton = ({
path,
key,
name,
}: { path: string; key: string; name: string }) => {
const { trackEvent } = usePlausibleTracker();
const onClick = () => {
trackEvent('command-bar', {
props: {
eventType: `click`,
source: 'recently-visited',
eventTarget: 'Pages',
pageType: name,
},
});
};
return (
<ListItemButton
key={key}
dense={true}
component={Link}
to={path}
sx={listItemButtonStyle}
onClick={onClick}
>
<StyledListItemIcon>
<ConditionallyRender
condition={path === '/projects'}
show={<StyledProjectIcon />}
elseShow={<IconRenderer path={path} />}
/>
</StyledListItemIcon>
<StyledListItemText>
<StyledButtonTypography color='textPrimary'>
{name}
</StyledButtonTypography>
</StyledListItemText>
</ListItemButton>
);
};
export const RecentlyVisitedProjectButton = ({
projectId,
key,
}: { projectId: string; key: string }) => {
const { trackEvent } = usePlausibleTracker();
const { project, loading } = useProjectOverview(projectId);
const projectDeleted = !project.name && !loading;
const onClick = () => {
trackEvent('command-bar', {
props: {
eventType: `click`,
source: 'recently-visited',
eventTarget: 'Projects',
},
});
};
if (projectDeleted) return null;
return (
<ListItemButton
key={key}
dense={true}
component={Link}
to={`/projects/${projectId}`}
sx={listItemButtonStyle}
onClick={onClick}
>
<StyledListItemIcon>
<StyledProjectIcon />
</StyledListItemIcon>
<StyledListItemText>
<StyledButtonTypography color='textPrimary'>
{project.name}
</StyledButtonTypography>
</StyledListItemText>
</ListItemButton>
);
};
export const RecentlyVisitedFeatureButton = ({
key,
projectId,
featureId,
}: {
key: string;
projectId: string;
featureId: string;
}) => {
const onClick = () => {
const { trackEvent } = usePlausibleTracker();
trackEvent('command-bar', {
props: {
eventType: `click`,
source: 'recently-visited',
eventTarget: 'Flags',
},
});
};
return (
<ListItemButton
key={key}
dense={true}
component={Link}
to={`/projects/${projectId}/features/${featureId}`}
sx={listItemButtonStyle}
onClick={onClick}
>
<StyledListItemIcon>
<Icon>{'flag'}</Icon>
</StyledListItemIcon>
<StyledListItemText>
<StyledButtonTypography color='textPrimary'>
{featureId}
</StyledButtonTypography>
</StyledListItemText>
</ListItemButton>
);
};
interface CommandResultGroupProps { interface CommandResultGroupProps {
icon: string; icon: string;
groupName: string; groupName: string;
items: CommandResultGroupItem[]; items?: CommandResultGroupItem[];
children?: React.ReactNode;
} }
export const CommandResultGroup = ({ export const CommandResultGroup = ({
icon, icon,
groupName, groupName,
items, items,
children,
}: CommandResultGroupProps) => { }: CommandResultGroupProps) => {
const { trackEvent } = usePlausibleTracker(); const { trackEvent } = usePlausibleTracker();
const slicedItems = items.slice(0, 3); if (!children && (!items || items.length === 0)) {
if (items.length === 0) {
return null; return null;
} }
const slicedItems = items?.slice(0, 3);
const onClick = (item: CommandResultGroupItem) => { const onClick = (item: CommandResultGroupItem) => {
trackEvent('command-bar', { trackEvent('command-bar', {
props: { props: {
@ -70,13 +209,15 @@ export const CommandResultGroup = ({
}, },
}); });
}; };
return ( return (
<> <StyledContainer>
<StyledTypography color='textSecondary'> <StyledTypography color='textSecondary'>
{groupName} {groupName}
</StyledTypography> </StyledTypography>
<List> <List>
{slicedItems.map((item, index) => ( {children}
{slicedItems?.map((item, index) => (
<ListItemButton <ListItemButton
key={`command-result-group-${groupName}-${index}`} key={`command-result-group-${groupName}-${index}`}
dense={true} dense={true}
@ -100,12 +241,14 @@ export const CommandResultGroup = ({
placement={'bottom-end'} placement={'bottom-end'}
> >
<StyledListItemText> <StyledListItemText>
<Typography>{item.name}</Typography> <StyledButtonTypography color='textPrimary'>
{item.name}
</StyledButtonTypography>
</StyledListItemText> </StyledListItemText>
</TooltipResolver> </TooltipResolver>
</ListItemButton> </ListItemButton>
))} ))}
</List> </List>
</> </StyledContainer>
); );
}; };