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 { Lock } from '@mui/icons-material'; | ||||
| import React from 'react'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { | ||||
|     TooltipResolver, | ||||
|     ITooltipResolverProps, | ||||
| @ -32,6 +31,22 @@ export interface IProjectPermissionButtonProps extends IPermissionButtonProps { | ||||
|     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> = | ||||
|     React.forwardRef((props, ref) => { | ||||
|         const access = useHasProjectEnvironmentAccess( | ||||
| @ -75,6 +90,7 @@ const BasePermissionButton: React.FC<IPermissionBaseButtonProps> = | ||||
|             ref | ||||
|         ) => { | ||||
|             const id = useId(); | ||||
|             const endIcon = getEndIcon(access, rest.endIcon, hideLockIcon); | ||||
| 
 | ||||
|             return ( | ||||
|                 <TooltipResolver | ||||
| @ -91,18 +107,7 @@ const BasePermissionButton: React.FC<IPermissionBaseButtonProps> = | ||||
|                             variant={variant} | ||||
|                             color={color} | ||||
|                             {...rest} | ||||
|                             endIcon={ | ||||
|                                 <> | ||||
|                                     <ConditionallyRender | ||||
|                                         condition={!access && !hideLockIcon} | ||||
|                                         show={<Lock titleAccess="Locked" />} | ||||
|                                         elseShow={ | ||||
|                                             Boolean(rest.endIcon) && | ||||
|                                             rest.endIcon | ||||
|                                         } | ||||
|                                     /> | ||||
|                                 </> | ||||
|                             } | ||||
|                             endIcon={endIcon} | ||||
|                         > | ||||
|                             {children} | ||||
|                         </Button> | ||||
|  | ||||
| @ -3,18 +3,25 @@ import { | ||||
|     formatStrategyName, | ||||
| } from 'utils/strategyNames'; | ||||
| import { styled, Tooltip } from '@mui/material'; | ||||
| import { IFeatureStrategy } from 'interfaces/strategy'; | ||||
| 
 | ||||
| interface IFeatureStrategyIconProps { | ||||
|     strategyName: string; | ||||
|     strategy: IFeatureStrategy; | ||||
| } | ||||
| 
 | ||||
| export const FeatureStrategyIcon = ({ | ||||
|     strategyName, | ||||
|     strategy, | ||||
| }: IFeatureStrategyIconProps) => { | ||||
|     const Icon = getFeatureStrategyIcon(strategyName); | ||||
|     const Icon = getFeatureStrategyIcon(strategy.name); | ||||
| 
 | ||||
|     return ( | ||||
|         <Tooltip title={formatStrategyName(strategyName)} arrow> | ||||
|         <Tooltip | ||||
|             title={ | ||||
|                 formatStrategyName(strategy.name) + | ||||
|                 (strategy.title ? ` - ${strategy.title}` : '') | ||||
|             } | ||||
|             arrow | ||||
|         > | ||||
|             <StyledIcon> | ||||
|                 <Icon /> | ||||
|             </StyledIcon> | ||||
|  | ||||
| @ -1,28 +1,8 @@ | ||||
| import { IFeatureStrategy } from 'interfaces/strategy'; | ||||
| import { FeatureStrategyIcon } from 'component/feature/FeatureStrategy/FeatureStrategyIcon/FeatureStrategyIcon'; | ||||
| import { styled } from '@mui/material'; | ||||
| 
 | ||||
| interface IFeatureStrategyIconsProps { | ||||
|     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> | ||||
|     ); | ||||
| }; | ||||
| import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; | ||||
| import { formatStrategyName } from 'utils/strategyNames'; | ||||
| 
 | ||||
| const StyledList = styled('ul')(() => ({ | ||||
|     all: 'unset', | ||||
| @ -36,3 +16,61 @@ const StyledListItem = styled('li')(() => ({ | ||||
|     minWidth: 30, | ||||
|     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']; | ||||
| } | ||||
| 
 | ||||
| const StyledStrategyMenu = styled('div')({ | ||||
|     flexShrink: 0, | ||||
| }); | ||||
| 
 | ||||
| const StyledAdditionalMenuButton = styled(PermissionButton)(({ theme }) => ({ | ||||
|     minWidth: 0, | ||||
|     width: theme.spacing(4.5), | ||||
| @ -71,7 +75,7 @@ export const FeatureStrategyMenu = ({ | ||||
|     ); | ||||
| 
 | ||||
|     return ( | ||||
|         <div onClick={event => event.stopPropagation()}> | ||||
|         <StyledStrategyMenu onClick={event => event.stopPropagation()}> | ||||
|             <PermissionButton | ||||
|                 permission={CREATE_FEATURE_STRATEGY} | ||||
|                 projectId={projectId} | ||||
| @ -118,6 +122,6 @@ export const FeatureStrategyMenu = ({ | ||||
|                     environmentId={environmentId} | ||||
|                 /> | ||||
|             </Popover> | ||||
|         </div> | ||||
|         </StyledStrategyMenu> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -123,6 +123,8 @@ const StyledButtonContainer = styled('div')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
|     marginTop: theme.spacing(2), | ||||
|     gap: theme.spacing(2), | ||||
|     flexWrap: 'wrap', | ||||
|     [theme.breakpoints.down(560)]: { | ||||
|         flexDirection: 'column', | ||||
|     }, | ||||
| @ -199,6 +201,11 @@ const FeatureOverviewEnvironment = ({ | ||||
|                                                 variant="outlined" | ||||
|                                                 size="small" | ||||
|                                             /> | ||||
|                                             <FeatureStrategyIcons | ||||
|                                                 strategies={ | ||||
|                                                     featureEnvironment?.strategies | ||||
|                                                 } | ||||
|                                             /> | ||||
|                                         </StyledButtonContainer> | ||||
|                                     } | ||||
|                                     elseShow={ | ||||
|  | ||||
| @ -97,9 +97,6 @@ exports[`renders an empty list correctly 1`] = ` | ||||
|                   type="button" | ||||
|                 > | ||||
|                   New tag type | ||||
|                   <span | ||||
|                     className="MuiButton-endIcon MuiButton-iconSizeMedium css-9tj150-MuiButton-endIcon" | ||||
|                   /> | ||||
|                   <span | ||||
|                     className="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root" | ||||
|                   /> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user