mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: responsive strategy icons (#4121)
https://linear.app/unleash/issue/2-1167/multiple-strategies-breaking-the-environment-card https://linear.app/unleash/issue/2-1179/buttons-have-an-extra-space-if-the-icon-its-not-visible This fixes the broken UI when we have too many strategies. Before: <img width="1500" alt="image" src="https://github.com/Unleash/unleash/assets/14320932/ddf2f636-965c-4527-b879-dba5c16d9630"> After: <img width="1303" alt="image" src="https://github.com/Unleash/unleash/assets/14320932/852c20c9-c5f4-4aa5-b8c0-e5bc5286c572"> We also added the new strategy type to the tooltips: <img width="519" alt="image" src="https://github.com/Unleash/unleash/assets/14320932/117ee00f-f2a7-4ecb-8596-44486a2870a2"> <img width="422" alt="image" src="https://github.com/Unleash/unleash/assets/14320932/4281a48c-4b6e-4100-86e2-29dfe9ce4cec"> This also fixes an extra margin we caught on our `PermissionButton` when it had no endIcon set. Co-authored by: @daveleek --------- Co-authored-by: David Leek <david@getunleash.io>
This commit is contained in:
		
							parent
							
								
									b6f405d1af
								
							
						
					
					
						commit
						73b4ae18c1
					
				| @ -1,7 +1,6 @@ | |||||||
| import { Button, ButtonProps } from '@mui/material'; | import { Button, ButtonProps } from '@mui/material'; | ||||||
| import { Lock } from '@mui/icons-material'; | import { Lock } from '@mui/icons-material'; | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; |  | ||||||
| import { | import { | ||||||
|     TooltipResolver, |     TooltipResolver, | ||||||
|     ITooltipResolverProps, |     ITooltipResolverProps, | ||||||
| @ -32,6 +31,22 @@ export interface IProjectPermissionButtonProps extends IPermissionButtonProps { | |||||||
|     environmentId: string; |     environmentId: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const getEndIcon = ( | ||||||
|  |     access: boolean, | ||||||
|  |     fallBackIcon?: React.ReactNode, | ||||||
|  |     hideLockIcon?: boolean | ||||||
|  | ): React.ReactNode => { | ||||||
|  |     if (!access && !hideLockIcon) { | ||||||
|  |         return <Lock titleAccess="Locked" />; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (fallBackIcon) { | ||||||
|  |         return fallBackIcon; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return null; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| const ProjectEnvironmentPermissionButton: React.FC<IProjectPermissionButtonProps> = | const ProjectEnvironmentPermissionButton: React.FC<IProjectPermissionButtonProps> = | ||||||
|     React.forwardRef((props, ref) => { |     React.forwardRef((props, ref) => { | ||||||
|         const access = useHasProjectEnvironmentAccess( |         const access = useHasProjectEnvironmentAccess( | ||||||
| @ -75,6 +90,7 @@ const BasePermissionButton: React.FC<IPermissionBaseButtonProps> = | |||||||
|             ref |             ref | ||||||
|         ) => { |         ) => { | ||||||
|             const id = useId(); |             const id = useId(); | ||||||
|  |             const endIcon = getEndIcon(access, rest.endIcon, hideLockIcon); | ||||||
| 
 | 
 | ||||||
|             return ( |             return ( | ||||||
|                 <TooltipResolver |                 <TooltipResolver | ||||||
| @ -91,18 +107,7 @@ const BasePermissionButton: React.FC<IPermissionBaseButtonProps> = | |||||||
|                             variant={variant} |                             variant={variant} | ||||||
|                             color={color} |                             color={color} | ||||||
|                             {...rest} |                             {...rest} | ||||||
|                             endIcon={ |                             endIcon={endIcon} | ||||||
|                                 <> |  | ||||||
|                                     <ConditionallyRender |  | ||||||
|                                         condition={!access && !hideLockIcon} |  | ||||||
|                                         show={<Lock titleAccess="Locked" />} |  | ||||||
|                                         elseShow={ |  | ||||||
|                                             Boolean(rest.endIcon) && |  | ||||||
|                                             rest.endIcon |  | ||||||
|                                         } |  | ||||||
|                                     /> |  | ||||||
|                                 </> |  | ||||||
|                             } |  | ||||||
|                         > |                         > | ||||||
|                             {children} |                             {children} | ||||||
|                         </Button> |                         </Button> | ||||||
|  | |||||||
| @ -3,18 +3,25 @@ import { | |||||||
|     formatStrategyName, |     formatStrategyName, | ||||||
| } from 'utils/strategyNames'; | } from 'utils/strategyNames'; | ||||||
| import { styled, Tooltip } from '@mui/material'; | import { styled, Tooltip } from '@mui/material'; | ||||||
|  | import { IFeatureStrategy } from 'interfaces/strategy'; | ||||||
| 
 | 
 | ||||||
| interface IFeatureStrategyIconProps { | interface IFeatureStrategyIconProps { | ||||||
|     strategyName: string; |     strategy: IFeatureStrategy; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const FeatureStrategyIcon = ({ | export const FeatureStrategyIcon = ({ | ||||||
|     strategyName, |     strategy, | ||||||
| }: IFeatureStrategyIconProps) => { | }: IFeatureStrategyIconProps) => { | ||||||
|     const Icon = getFeatureStrategyIcon(strategyName); |     const Icon = getFeatureStrategyIcon(strategy.name); | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <Tooltip title={formatStrategyName(strategyName)} arrow> |         <Tooltip | ||||||
|  |             title={ | ||||||
|  |                 formatStrategyName(strategy.name) + | ||||||
|  |                 (strategy.title ? ` - ${strategy.title}` : '') | ||||||
|  |             } | ||||||
|  |             arrow | ||||||
|  |         > | ||||||
|             <StyledIcon> |             <StyledIcon> | ||||||
|                 <Icon /> |                 <Icon /> | ||||||
|             </StyledIcon> |             </StyledIcon> | ||||||
|  | |||||||
| @ -1,28 +1,8 @@ | |||||||
| import { IFeatureStrategy } from 'interfaces/strategy'; | import { IFeatureStrategy } from 'interfaces/strategy'; | ||||||
| import { FeatureStrategyIcon } from 'component/feature/FeatureStrategy/FeatureStrategyIcon/FeatureStrategyIcon'; | import { FeatureStrategyIcon } from 'component/feature/FeatureStrategy/FeatureStrategyIcon/FeatureStrategyIcon'; | ||||||
| import { styled } from '@mui/material'; | import { styled } from '@mui/material'; | ||||||
| 
 | import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; | ||||||
| interface IFeatureStrategyIconsProps { | import { formatStrategyName } from 'utils/strategyNames'; | ||||||
|     strategies: IFeatureStrategy[] | undefined; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const FeatureStrategyIcons = ({ |  | ||||||
|     strategies, |  | ||||||
| }: IFeatureStrategyIconsProps) => { |  | ||||||
|     if (!strategies?.length) { |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ( |  | ||||||
|         <StyledList aria-label="Feature strategies"> |  | ||||||
|             {strategies.map(strategy => ( |  | ||||||
|                 <StyledListItem key={strategy.id}> |  | ||||||
|                     <FeatureStrategyIcon strategyName={strategy.name} /> |  | ||||||
|                 </StyledListItem> |  | ||||||
|             ))} |  | ||||||
|         </StyledList> |  | ||||||
|     ); |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| const StyledList = styled('ul')(() => ({ | const StyledList = styled('ul')(() => ({ | ||||||
|     all: 'unset', |     all: 'unset', | ||||||
| @ -36,3 +16,61 @@ const StyledListItem = styled('li')(() => ({ | |||||||
|     minWidth: 30, |     minWidth: 30, | ||||||
|     textAlign: 'center', |     textAlign: 'center', | ||||||
| })); | })); | ||||||
|  | 
 | ||||||
|  | const StyledItem = styled('div')(({ theme }) => ({ | ||||||
|  |     display: 'flex', | ||||||
|  |     flexDirection: 'row', | ||||||
|  |     alignItems: 'center', | ||||||
|  |     gap: theme.spacing(1), | ||||||
|  | })); | ||||||
|  | 
 | ||||||
|  | const THRESHOLD = 5; | ||||||
|  | 
 | ||||||
|  | interface IFeatureStrategyIconsProps { | ||||||
|  |     strategies: IFeatureStrategy[] | undefined; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const FeatureStrategyIcons = ({ | ||||||
|  |     strategies, | ||||||
|  | }: IFeatureStrategyIconsProps) => { | ||||||
|  |     if (!strategies?.length) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (strategies.length > THRESHOLD + 1) { | ||||||
|  |         return ( | ||||||
|  |             <StyledList aria-label="Feature strategies"> | ||||||
|  |                 {strategies.slice(0, THRESHOLD).map(strategy => ( | ||||||
|  |                     <StyledListItem key={strategy.id}> | ||||||
|  |                         <FeatureStrategyIcon strategy={strategy} /> | ||||||
|  |                     </StyledListItem> | ||||||
|  |                 ))} | ||||||
|  |                 <TooltipLink | ||||||
|  |                     tooltip={strategies.slice(THRESHOLD).map(strategy => ( | ||||||
|  |                         <StyledListItem key={strategy.id}> | ||||||
|  |                             <StyledItem> | ||||||
|  |                                 <FeatureStrategyIcon strategy={strategy} />{' '} | ||||||
|  |                                 {formatStrategyName(strategy.name) + | ||||||
|  |                                     (strategy.title | ||||||
|  |                                         ? ` - ${strategy.title}` | ||||||
|  |                                         : '')} | ||||||
|  |                             </StyledItem> | ||||||
|  |                         </StyledListItem> | ||||||
|  |                     ))} | ||||||
|  |                 > | ||||||
|  |                     (+{strategies.length - THRESHOLD}) | ||||||
|  |                 </TooltipLink> | ||||||
|  |             </StyledList> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <StyledList aria-label="Feature strategies"> | ||||||
|  |             {strategies.map(strategy => ( | ||||||
|  |                 <StyledListItem key={strategy.id}> | ||||||
|  |                     <FeatureStrategyIcon strategy={strategy} /> | ||||||
|  |                 </StyledListItem> | ||||||
|  |             ))} | ||||||
|  |         </StyledList> | ||||||
|  |     ); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -20,6 +20,10 @@ interface IFeatureStrategyMenuProps { | |||||||
|     size?: IPermissionButtonProps['size']; |     size?: IPermissionButtonProps['size']; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const StyledStrategyMenu = styled('div')({ | ||||||
|  |     flexShrink: 0, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| const StyledAdditionalMenuButton = styled(PermissionButton)(({ theme }) => ({ | const StyledAdditionalMenuButton = styled(PermissionButton)(({ theme }) => ({ | ||||||
|     minWidth: 0, |     minWidth: 0, | ||||||
|     width: theme.spacing(4.5), |     width: theme.spacing(4.5), | ||||||
| @ -71,7 +75,7 @@ export const FeatureStrategyMenu = ({ | |||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <div onClick={event => event.stopPropagation()}> |         <StyledStrategyMenu onClick={event => event.stopPropagation()}> | ||||||
|             <PermissionButton |             <PermissionButton | ||||||
|                 permission={CREATE_FEATURE_STRATEGY} |                 permission={CREATE_FEATURE_STRATEGY} | ||||||
|                 projectId={projectId} |                 projectId={projectId} | ||||||
| @ -118,6 +122,6 @@ export const FeatureStrategyMenu = ({ | |||||||
|                     environmentId={environmentId} |                     environmentId={environmentId} | ||||||
|                 /> |                 /> | ||||||
|             </Popover> |             </Popover> | ||||||
|         </div> |         </StyledStrategyMenu> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -123,6 +123,8 @@ const StyledButtonContainer = styled('div')(({ theme }) => ({ | |||||||
|     display: 'flex', |     display: 'flex', | ||||||
|     alignItems: 'center', |     alignItems: 'center', | ||||||
|     marginTop: theme.spacing(2), |     marginTop: theme.spacing(2), | ||||||
|  |     gap: theme.spacing(2), | ||||||
|  |     flexWrap: 'wrap', | ||||||
|     [theme.breakpoints.down(560)]: { |     [theme.breakpoints.down(560)]: { | ||||||
|         flexDirection: 'column', |         flexDirection: 'column', | ||||||
|     }, |     }, | ||||||
| @ -199,6 +201,11 @@ const FeatureOverviewEnvironment = ({ | |||||||
|                                                 variant="outlined" |                                                 variant="outlined" | ||||||
|                                                 size="small" |                                                 size="small" | ||||||
|                                             /> |                                             /> | ||||||
|  |                                             <FeatureStrategyIcons | ||||||
|  |                                                 strategies={ | ||||||
|  |                                                     featureEnvironment?.strategies | ||||||
|  |                                                 } | ||||||
|  |                                             /> | ||||||
|                                         </StyledButtonContainer> |                                         </StyledButtonContainer> | ||||||
|                                     } |                                     } | ||||||
|                                     elseShow={ |                                     elseShow={ | ||||||
|  | |||||||
| @ -97,9 +97,6 @@ exports[`renders an empty list correctly 1`] = ` | |||||||
|                   type="button" |                   type="button" | ||||||
|                 > |                 > | ||||||
|                   New tag type |                   New tag type | ||||||
|                   <span |  | ||||||
|                     className="MuiButton-endIcon MuiButton-iconSizeMedium css-9tj150-MuiButton-endIcon" |  | ||||||
|                   /> |  | ||||||
|                   <span |                   <span | ||||||
|                     className="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root" |                     className="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root" | ||||||
|                   /> |                   /> | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user