1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01: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 { useOnClickOutside } from 'hooks/useOnClickOutside';
import { useOnBlur } from 'hooks/useOnBlur';
import { CommandRecent } from './CommandRecent';
import { useRecentlyVisited } from 'hooks/useRecentlyVisited';
import {
CommandResultGroup,
@ -26,6 +25,8 @@ import { useAsyncDebounce } from 'react-table';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { CommandFeatures } from './CommandFeatures';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { CommandRecent } from './CommandRecent';
import { CommandPages } from './CommandPages';
export const CommandResultsPaper = styled(Paper)(({ theme }) => ({
position: 'absolute',
@ -259,11 +260,7 @@ export const CommandBar = () => {
icon={'flag'}
items={searchedProjects}
/>
<CommandResultGroup
groupName={'Pages'}
icon={'flag'}
items={searchedPages}
/>
<CommandPages items={searchedPages} />
</CommandResultsPaper>
}
elseShow={

View File

@ -1,49 +1,16 @@
import {
List,
ListItemButton,
ListItemIcon,
ListItemText,
styled,
Typography,
} from '@mui/material';
CommandResultGroup,
listItemButtonStyle,
StyledButtonTypography,
StyledListItemIcon,
StyledListItemText,
} from './RecentlyVisited/CommandResultGroup';
import { ListItemButton } from '@mui/material';
import { Link } from 'react-router-dom';
import { IconRenderer } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
import type { Theme } from '@mui/material/styles/createTheme';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
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 {
icon: JSX.Element;
name: string;
@ -93,38 +60,34 @@ export const CommandPageSuggestions = ({
});
};
return (
<StyledContainer>
<StyledTypography color='textSecondary'>Pages</StyledTypography>
<List>
{pageItems.map((item, index) => (
<ListItemButton
key={`recently-visited-${index}`}
dense={true}
component={Link}
to={item.path}
onClick={() => {
onClick(item);
}}
sx={listItemButtonStyle}
<CommandResultGroup icon='pages' groupName='Pages'>
{pageItems.map((item, index) => (
<ListItemButton
key={`recently-visited-${index}`}
dense={true}
component={Link}
to={item.path}
onClick={() => {
onClick(item);
}}
sx={listItemButtonStyle}
>
<StyledListItemIcon
sx={(theme) => ({
fontSize: theme.fontSizes.smallBody,
minWidth: theme.spacing(0.5),
margin: theme.spacing(0, 1, 0, 0),
})}
>
<StyledListItemIcon
sx={(theme) => ({
color: theme.palette.primary.main,
fontSize: theme.fontSizes.smallBody,
minWidth: theme.spacing(0.5),
margin: theme.spacing(0, 1, 0, 0),
})}
>
{item.icon}
</StyledListItemIcon>
<StyledListItemText>
<StyledButtonTypography color='textPrimary'>
{item.name}
</StyledButtonTypography>
</StyledListItemText>
</ListItemButton>
))}
</List>
</StyledContainer>
{item.icon}
</StyledListItemIcon>
<StyledListItemText>
<StyledButtonTypography color='textPrimary'>
{item.name}
</StyledButtonTypography>
</StyledListItemText>
</ListItemButton>
))}
</CommandResultGroup>
);
};

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 {
Icon,
List,
ListItemButton,
ListItemIcon,
ListItemText,
styled,
Typography,
} from '@mui/material';
import { Link } from 'react-router-dom';
import {
IconRenderer,
StyledProjectIcon,
} from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
CommandResultGroup,
RecentlyVisitedFeatureButton,
RecentlyVisitedPathButton,
RecentlyVisitedProjectButton,
} from './RecentlyVisited/CommandResultGroup';
import { List } from '@mui/material';
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 = (
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 = ({
lastVisited,
routes,
@ -218,11 +48,8 @@ export const CommandRecent = ({
toListItemButton(item, routes, index),
);
return (
<StyledContainer>
<StyledTypography color='textSecondary'>
Recently visited
</StyledTypography>
<CommandResultGroup icon='default' groupName='Quick suggestions'>
<List>{buttons}</List>
</StyledContainer>
</CommandResultGroup>
);
};

View File

@ -10,30 +10,43 @@ import {
import { Link } from 'react-router-dom';
import type { Theme } from '@mui/material/styles/createTheme';
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 { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
const listItemButtonStyle = (theme: Theme) => ({
borderRadius: theme.spacing(0.5),
export const listItemButtonStyle = (theme: Theme) => ({
border: `1px 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}`,
},
});
const StyledContainer = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(3),
}));
const StyledTypography = styled(Typography)(({ theme }) => ({
export const StyledTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.bodySize,
padding: theme.spacing(0, 3),
padding: theme.spacing(0, 2.5),
}));
const StyledListItemIcon = styled(ListItemIcon)(({ theme }) => ({
minWidth: theme.spacing(4),
margin: theme.spacing(0.25, 0),
export const StyledListItemIcon = styled(ListItemIcon)(({ theme }) => ({
minWidth: theme.spacing(0.5),
margin: theme.spacing(0, 1, 0, 0),
}));
const StyledListItemText = styled(ListItemText)(({ theme }) => ({
export const StyledListItemText = styled(ListItemText)(({ theme }) => ({
margin: 0,
fontSize: theme.fontSizes.bodySize,
}));
export const StyledButtonTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.bodySize,
}));
export interface CommandResultGroupItem {
@ -42,24 +55,150 @@ export interface CommandResultGroupItem {
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 {
icon: string;
groupName: string;
items: CommandResultGroupItem[];
items?: CommandResultGroupItem[];
children?: React.ReactNode;
}
export const CommandResultGroup = ({
icon,
groupName,
items,
children,
}: CommandResultGroupProps) => {
const { trackEvent } = usePlausibleTracker();
const slicedItems = items.slice(0, 3);
if (items.length === 0) {
if (!children && (!items || items.length === 0)) {
return null;
}
const slicedItems = items?.slice(0, 3);
const onClick = (item: CommandResultGroupItem) => {
trackEvent('command-bar', {
props: {
@ -70,13 +209,15 @@ export const CommandResultGroup = ({
},
});
};
return (
<>
<StyledContainer>
<StyledTypography color='textSecondary'>
{groupName}
</StyledTypography>
<List>
{slicedItems.map((item, index) => (
{children}
{slicedItems?.map((item, index) => (
<ListItemButton
key={`command-result-group-${groupName}-${index}`}
dense={true}
@ -100,12 +241,14 @@ export const CommandResultGroup = ({
placement={'bottom-end'}
>
<StyledListItemText>
<Typography>{item.name}</Typography>
<StyledButtonTypography color='textPrimary'>
{item.name}
</StyledButtonTypography>
</StyledListItemText>
</TooltipResolver>
</ListItemButton>
))}
</List>
</>
</StyledContainer>
);
};