Skip to content

Commit

Permalink
feat: better tooltip links (#2891)
Browse files Browse the repository at this point in the history
https://linear.app/unleash/issue/2-576/improve-how-text-that-has-tooltip-should-look-so-they-are-not


![image](https://user-images.githubusercontent.com/14320932/212140467-46d4f7f9-b5c1-40ea-9748-4a6ccc7950bb.png)


![image](https://user-images.githubusercontent.com/14320932/212140316-d6e88bc0-c26e-436b-855f-5f6e8697aed8.png)

- Adapts the existing `HtmlTooltip` component to support setting
`maxHeight` and `maxWidth` properties;
- Introduces a new common component: `TooltipLink`;
- Adapts SA (tokens), features (tags), variants (overrides, payloads)
and project access (role and role description);
- Role description in project access now uses this component instead of
the old jankier popover component;
  • Loading branch information
nunogois committed Jan 16, 2023
1 parent 005e5b1 commit 4286103
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 170 deletions.
@@ -1,22 +1,16 @@
import { VFC } from 'react';
import { Link, styled, Typography } from '@mui/material';
import { styled, Typography } from '@mui/material';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { IServiceAccount } from 'interfaces/service-account';
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';

const StyledItem = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallerBody,
}));

const StyledLink = styled(Link, {
shouldForwardProp: prop => prop !== 'highlighted',
})<{ highlighted?: boolean }>(({ theme, highlighted }) => ({
backgroundColor: highlighted ? theme.palette.highlight : 'transparent',
}));

interface IServiceAccountTokensCellProps {
serviceAccount: IServiceAccount;
value: string;
Expand All @@ -35,8 +29,8 @@ export const ServiceAccountTokensCell: VFC<IServiceAccountTokensCellProps> = ({

return (
<TextCell>
<HtmlTooltip
title={
<TooltipLink
tooltip={
<>
{serviceAccount.tokens?.map(({ id, description }) => (
<StyledItem key={id}>
Expand All @@ -47,19 +41,15 @@ export const ServiceAccountTokensCell: VFC<IServiceAccountTokensCellProps> = ({
))}
</>
}
highlighted={
searchQuery.length > 0 &&
value.toLowerCase().includes(searchQuery.toLowerCase())
}
>
<StyledLink
underline="always"
highlighted={
searchQuery.length > 0 &&
value.toLowerCase().includes(searchQuery.toLowerCase())
}
>
{serviceAccount.tokens?.length === 1
? '1 token'
: `${serviceAccount.tokens?.length} tokens`}
</StyledLink>
</HtmlTooltip>
{serviceAccount.tokens?.length === 1
? '1 token'
: `${serviceAccount.tokens?.length} tokens`}
</TooltipLink>
</TextCell>
);
};
59 changes: 37 additions & 22 deletions frontend/src/component/common/HtmlTooltip/HtmlTooltip.tsx
@@ -1,29 +1,44 @@
import { styled, Tooltip, tooltipClasses, TooltipProps } from '@mui/material';
import { SpacingArgument } from '@mui/system/createTheme/createSpacing';

const StyledHtmlTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
))(({ theme }) => ({
maxWidth: theme.spacing(37.5),
[`& .${tooltipClasses.tooltip}`]: {
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(1, 1.5),
borderRadius: theme.shape.borderRadiusMedium,
boxShadow: theme.shadows[2],
color: theme.palette.text.primary,
fontWeight: theme.fontWeight.medium,
maxWidth: 'inherit',
border: `1px solid ${theme.palette.lightBorder}`,
},
[`& .${tooltipClasses.arrow}`]: {
'&:before': {
const StyledHtmlTooltip = styled(
({ className, maxWidth, maxHeight, ...props }: IHtmlTooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
),
{
shouldForwardProp: prop => prop !== 'maxWidth' && prop !== 'maxHeight',
}
)<{ maxWidth?: SpacingArgument; maxHeight?: SpacingArgument }>(
({ theme, maxWidth, maxHeight }) => ({
maxWidth: maxWidth || theme.spacing(37.5),
[`& .${tooltipClasses.tooltip}`]: {
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(1, 1.5),
borderRadius: theme.shape.borderRadiusMedium,
boxShadow: theme.shadows[2],
color: theme.palette.text.primary,
fontWeight: theme.fontWeight.medium,
maxWidth: 'inherit',
border: `1px solid ${theme.palette.lightBorder}`,
maxHeight: maxHeight || theme.spacing(37.5),
overflow: 'auto',
},
color: theme.palette.background.paper,
},
}));
[`& .${tooltipClasses.arrow}`]: {
'&:before': {
border: `1px solid ${theme.palette.lightBorder}`,
},
color: theme.palette.background.paper,
},
})
);

export interface IHtmlTooltipProps extends TooltipProps {
maxWidth?: SpacingArgument;
maxHeight?: SpacingArgument;
}

export const HtmlTooltip = (props: TooltipProps) => (
export const HtmlTooltip = (props: IHtmlTooltipProps) => (
<StyledHtmlTooltip {...props}>{props.children}</StyledHtmlTooltip>
);
@@ -1,21 +1,15 @@
import { VFC } from 'react';
import { FeatureSchema } from 'openapi';
import { Link, styled, Typography } from '@mui/material';
import { styled, Typography } from '@mui/material';
import { TextCell } from '../TextCell/TextCell';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';

const StyledTag = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallerBody,
}));

const StyledLink = styled(Link, {
shouldForwardProp: prop => prop !== 'highlighted',
})<{ highlighted?: boolean }>(({ theme, highlighted }) => ({
backgroundColor: highlighted ? theme.palette.highlight : 'transparent',
}));

interface IFeatureTagCellProps {
row: {
original: FeatureSchema;
Expand All @@ -31,8 +25,12 @@ export const FeatureTagCell: VFC<IFeatureTagCellProps> = ({ row, value }) => {

return (
<TextCell>
<HtmlTooltip
title={
<TooltipLink
highlighted={
searchQuery.length > 0 &&
value.toLowerCase().includes(searchQuery.toLowerCase())
}
tooltip={
<>
{row.original.tags?.map(tag => (
<StyledTag key={tag.type + tag.value}>
Expand All @@ -44,18 +42,10 @@ export const FeatureTagCell: VFC<IFeatureTagCellProps> = ({ row, value }) => {
</>
}
>
<StyledLink
underline="always"
highlighted={
searchQuery.length > 0 &&
value.toLowerCase().includes(searchQuery.toLowerCase())
}
>
{row.original.tags?.length === 1
? '1 tag'
: `${row.original.tags?.length} tags`}
</StyledLink>
</HtmlTooltip>
{row.original.tags?.length === 1
? '1 tag'
: `${row.original.tags?.length} tags`}
</TooltipLink>
</TextCell>
);
};
34 changes: 34 additions & 0 deletions frontend/src/component/common/TooltipLink/TooltipLink.tsx
@@ -0,0 +1,34 @@
import { ReactNode } from 'react';
import { Link, LinkProps, styled, TooltipProps } from '@mui/material';
import { HtmlTooltip, IHtmlTooltipProps } from '../HtmlTooltip/HtmlTooltip';

const StyledLink = styled(Link, {
shouldForwardProp: prop => prop !== 'highlighted',
})<{ highlighted?: boolean }>(({ theme, highlighted }) => ({
backgroundColor: highlighted ? theme.palette.highlight : 'transparent',
color: theme.palette.text.primary,
textDecorationColor: theme.palette.text.disabled,
textDecorationStyle: 'dashed',
textUnderlineOffset: theme.spacing(0.5),
}));

interface ITooltipLinkProps extends LinkProps {
tooltip: ReactNode;
highlighted?: boolean;
tooltipProps?: Omit<IHtmlTooltipProps, 'title' | 'children'>;
children: ReactNode;
}

export const TooltipLink = ({
tooltip,
highlighted,
tooltipProps,
children,
...props
}: ITooltipLinkProps) => (
<HtmlTooltip title={tooltip} {...tooltipProps}>
<StyledLink highlighted={highlighted} {...props}>
{children}
</StyledLink>
</HtmlTooltip>
);
@@ -1,20 +1,14 @@
import { Link, styled, Typography } from '@mui/material';
import { styled, Typography } from '@mui/material';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
import { IOverride } from 'interfaces/featureToggle';

const StyledItem = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallerBody,
}));

const StyledLink = styled(Link, {
shouldForwardProp: prop => prop !== 'highlighted',
})<{ highlighted?: boolean }>(({ theme, highlighted }) => ({
backgroundColor: highlighted ? theme.palette.highlight : 'transparent',
}));

interface IOverridesCellProps {
value?: IOverride[];
}
Expand All @@ -29,8 +23,8 @@ export const OverridesCell = ({ value: overrides }: IOverridesCellProps) => {

return (
<TextCell>
<HtmlTooltip
title={
<TooltipLink
tooltip={
<>
{overrides.map((override, index) => (
<StyledItem key={override.contextName + index}>
Expand All @@ -41,23 +35,19 @@ export const OverridesCell = ({ value: overrides }: IOverridesCellProps) => {
))}
</>
}
highlighted={
searchQuery.length > 0 &&
overrides
?.map(overrideToString)
.join('\n')
.toLowerCase()
.includes(searchQuery.toLowerCase())
}
>
<StyledLink
underline="always"
highlighted={
searchQuery.length > 0 &&
overrides
?.map(overrideToString)
.join('\n')
.toLowerCase()
.includes(searchQuery.toLowerCase())
}
>
{overrides.length === 1
? '1 override'
: `${overrides.length} overrides`}
</StyledLink>
</HtmlTooltip>
{overrides.length === 1
? '1 override'
: `${overrides.length} overrides`}
</TooltipLink>
</TextCell>
);
};
@@ -1,21 +1,15 @@
import { Link, styled, Typography } from '@mui/material';
import { styled, Typography } from '@mui/material';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
import { IPayload } from 'interfaces/featureToggle';

const StyledItem = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallerBody,
whiteSpace: 'pre-wrap',
}));

const StyledLink = styled(Link, {
shouldForwardProp: prop => prop !== 'highlighted',
})<{ highlighted?: boolean }>(({ theme, highlighted }) => ({
backgroundColor: highlighted ? theme.palette.highlight : 'transparent',
}));

interface IPayloadCellProps {
value?: IPayload;
}
Expand All @@ -35,8 +29,8 @@ export const PayloadCell = ({ value: payload }: IPayloadCellProps) => {

return (
<TextCell>
<HtmlTooltip
title={
<TooltipLink
tooltip={
<>
<StyledItem>
<Highlighter search={searchQuery}>
Expand All @@ -45,19 +39,15 @@ export const PayloadCell = ({ value: payload }: IPayloadCellProps) => {
</StyledItem>
</>
}
highlighted={
searchQuery.length > 0 &&
payload.value
.toLowerCase()
.includes(searchQuery.toLowerCase())
}
>
<StyledLink
underline="always"
highlighted={
searchQuery.length > 0 &&
payload.value
.toLowerCase()
.includes(searchQuery.toLowerCase())
}
>
{payload.type}
</StyledLink>
</HtmlTooltip>
{payload.type}
</TooltipLink>
</TextCell>
);
};
Expand Up @@ -8,12 +8,19 @@ export const ProjectRoleDescriptionEnvironmentPermissions = ({
permissions,
}: IProjectRoleDescriptionEnvironmentPermissionsProps) => (
<>
{permissions
.filter((permission: any) => permission.environment === environment)
.map((permission: any) => permission.displayName)
{[
...new Set(
permissions
.filter(
(permission: any) =>
permission.environment === environment
)
.map((permission: any) => permission.displayName)
),
]
.sort()
.map((permission: any) => (
<p key={permission}>{permission}</p>
<p key={`${environment}-${permission}`}>{permission}</p>
))}
</>
);

0 comments on commit 4286103

Please sign in to comment.