1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00

chore: adjust env dropdown (#9382)

Adjusts styling of the env dropdown now that we have both release plans
and strategies.

Key points:
- simplifies strategy separator, removes inherent height. Also: extracts
it from the draggable component (it has no business knowing whether to
add that or not)
- Puts release plans and strategies in the same list so that it becomes:
```markdown
- Release plan
  - strategy 1
  - strategy 2
- (OR) Strategy A
- (OR) Strategy B
```
- Adjusts some padding around to make it line up properly
- Swaps a couple conditional renders for ternaries


Rendered:

![image](https://github.com/user-attachments/assets/d6d5cd56-572f-419e-90ed-6449a63fdc96)

## Still todo:

Handle cases where you have >50 strats and we show the warning etc. It's
a little trickier because of how it interacts with release plans, so I
wanna leave that for later.

I'm also unsure about how we handle spacing today. All the little items
have their own different spacing and I'm not sure it won't get out of
sync, but I'm also not sure how else to handle it. We should look at it
later.
This commit is contained in:
Thomas Heartman 2025-02-27 15:28:41 +01:00 committed by GitHub
parent 426f53cd8d
commit 63d4b8b0e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 132 additions and 197 deletions

View File

@ -1,57 +1,20 @@
import { Box, styled, useTheme } from '@mui/material'; import { styled } from '@mui/material';
interface IStrategySeparatorProps { const Chip = styled('div')(({ theme }) => ({
text: 'AND' | 'OR';
}
const StyledAnd = styled('div')(({ theme }) => ({
padding: theme.spacing(0.75, 1), padding: theme.spacing(0.75, 1),
color: theme.palette.text.primary,
fontSize: theme.fontSizes.smallerBody, fontSize: theme.fontSizes.smallerBody,
backgroundColor: theme.palette.background.elevation2,
position: 'absolute', position: 'absolute',
zIndex: theme.zIndex.fab, zIndex: theme.zIndex.fab,
top: '50%', top: 0,
left: theme.spacing(2),
transform: 'translateY(-50%)', transform: 'translateY(-50%)',
lineHeight: 1, lineHeight: 1,
borderRadius: theme.shape.borderRadiusLarge, borderRadius: theme.shape.borderRadiusLarge,
}));
const StyledOr = styled(StyledAnd)(({ theme }) => ({
fontWeight: 'bold', fontWeight: 'bold',
backgroundColor: theme.palette.background.alternative, backgroundColor: theme.palette.background.alternative,
color: theme.palette.primary.contrastText, color: theme.palette.primary.contrastText,
left: theme.spacing(4), left: theme.spacing(4),
})); }));
const StyledSeparator = styled('hr')(({ theme }) => ({ export const StrategySeparator = () => {
border: 0, return <Chip>OR</Chip>;
borderTop: `1px solid ${theme.palette.divider}`,
margin: 0,
position: 'absolute',
top: '50%',
width: '100%',
}));
export const StrategySeparator = ({ text }: IStrategySeparatorProps) => {
const theme = useTheme();
return (
<Box
sx={{
height: theme.spacing(text === 'AND' ? 1 : 1.5),
position: 'relative',
}}
aria-hidden={true}
>
{text === 'AND' ? (
<StyledAnd>{text}</StyledAnd>
) : (
<>
<StyledSeparator />
<StyledOr>{text}</StyledOr>
</>
)}
</Box>
);
}; };

View File

@ -8,8 +8,6 @@ import { Alert, Pagination, styled } from '@mui/material';
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi'; import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategyDraggableItem } from './StrategyDraggableItem/LegacyStrategyDraggableItem';
import type { IFeatureEnvironment } from 'interfaces/featureToggle'; import type { IFeatureEnvironment } from 'interfaces/featureToggle';
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty'; import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
@ -22,10 +20,9 @@ import type { IFeatureStrategy } from 'interfaces/strategy';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag'; import { useUiFlag } from 'hooks/useUiFlag';
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans'; import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
import { Badge } from 'component/common/Badge/Badge'; import { StrategyDraggableItem } from './StrategyDraggableItem/StrategyDraggableItem';
import { StrategyDraggableItem as NewStrategyDraggableItem } from './StrategyDraggableItem/StrategyDraggableItem';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import { ReleasePlan } from '../../../ReleasePlan/ReleasePlan'; import { ReleasePlan } from '../../../ReleasePlan/ReleasePlan';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
interface IEnvironmentAccordionBodyProps { interface IEnvironmentAccordionBodyProps {
isDisabled: boolean; isDisabled: boolean;
@ -33,41 +30,43 @@ interface IEnvironmentAccordionBodyProps {
otherEnvironments?: IFeatureEnvironment['name'][]; otherEnvironments?: IFeatureEnvironment['name'][];
} }
const StyledAccordionBody = styled('div')(({ theme }) => ({
width: '100%',
position: 'relative',
paddingBottom: theme.spacing(2),
}));
const StyledAccordionBodyInnerContainer = styled('div')(({ theme }) => ({ const StyledAccordionBodyInnerContainer = styled('div')(({ theme }) => ({
[theme.breakpoints.down(400)]: { [theme.breakpoints.down(400)]: {
padding: theme.spacing(1), padding: theme.spacing(1),
}, },
})); }));
const StyledBadge = styled(Badge)(({ theme }) => ({ const StyledContentList = styled('ol')({
backgroundColor: theme.palette.primary.light,
border: 'none',
padding: theme.spacing(0.75, 1.5),
borderRadius: theme.shape.borderRadiusLarge,
color: theme.palette.common.white,
}));
const AdditionalStrategiesDiv = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: theme.spacing(2),
}));
const StyledStrategyList = styled('ol')({
listStyle: 'none', listStyle: 'none',
padding: 0, padding: 0,
margin: 0, margin: 0,
}); });
const StyledReleasePlanList = styled(StyledStrategyList)(({ theme }) => ({ const StyledListItem = styled('li', {
background: theme.palette.background.elevation2, shouldForwardProp: (prop) => prop !== 'type',
})<{ type?: 'release plan' | 'strategy' }>(({ theme, type }) => ({
borderBottom: `1px solid ${theme.palette.divider}`,
background:
type === 'release plan'
? theme.palette.background.elevation2
: theme.palette.background.elevation1,
position: 'relative',
paddingBlock: theme.spacing(2.5),
'&:first-of-type': {
paddingTop: theme.spacing(1),
},
}));
const PaginatedStrategyContainer = styled('div')(({ theme }) => ({
paddingTop: theme.spacing(2.5),
position: 'relative',
display: 'flex',
flexFlow: 'column nowrap',
gap: theme.spacing(2),
}));
const StyledAlert = styled(Alert)(({ theme }) => ({
marginInline: theme.spacing(2), // should consider finding a variable here
})); }));
export const EnvironmentAccordionBody = ({ export const EnvironmentAccordionBody = ({
@ -232,36 +231,28 @@ export const EnvironmentAccordionBody = ({
}; };
return ( return (
<StyledAccordionBody> <div>
<StyledAccordionBodyInnerContainer> <StyledAccordionBodyInnerContainer>
<ConditionallyRender {releasePlans.length > 0 || strategies.length > 0 ? (
condition={releasePlans.length > 0 || strategies.length > 0} <StyledContentList>
show={
<>
<StyledReleasePlanList>
{releasePlans.map((plan) => ( {releasePlans.map((plan) => (
<li key={plan.id}> <StyledListItem type='release plan' key={plan.id}>
<ReleasePlan <ReleasePlan
plan={plan} plan={plan}
environmentIsDisabled={isDisabled} environmentIsDisabled={isDisabled}
/> />
</li> </StyledListItem>
))} ))}
</StyledReleasePlanList> {strategies.length < 50 || !manyStrategiesPagination ? (
{releasePlans.length > 0 && <StyledContentList>
strategies.length > 0 ? (
<StrategySeparator text='OR' />
) : null}
<ConditionallyRender
condition={
strategies.length < 50 ||
!manyStrategiesPagination
}
show={
<StyledStrategyList>
{strategies.map((strategy, index) => ( {strategies.map((strategy, index) => (
<li key={strategy.id}> <StyledListItem key={strategy.id}>
<NewStrategyDraggableItem {index > 0 ||
releasePlans.length > 0 ? (
<StrategySeparator />
) : null}
<StrategyDraggableItem
strategy={strategy} strategy={strategy}
index={index} index={index}
environmentName={ environmentName={
@ -271,39 +262,35 @@ export const EnvironmentAccordionBody = ({
otherEnvironments otherEnvironments
} }
isDragging={ isDragging={
dragItem?.id === dragItem?.id === strategy.id
strategy.id
} }
onDragStartRef={ onDragStartRef={onDragStartRef}
onDragStartRef onDragOver={onDragOver(strategy.id)}
}
onDragOver={onDragOver(
strategy.id,
)}
onDragEnd={onDragEnd} onDragEnd={onDragEnd}
/> />
</li> </StyledListItem>
))} ))}
</StyledStrategyList> </StyledContentList>
} ) : (
elseShow={ <PaginatedStrategyContainer>
<> <StyledAlert severity='warning'>
<Alert severity='error'> We noticed you're using a high number of
We noticed you're using a high activation strategies. To ensure a more
number of activation strategies. To targeted approach, consider leveraging
ensure a more targeted approach, constraints or segments.
consider leveraging constraints or </StyledAlert>
segments. <StyledContentList>
</Alert>
<br />
<StyledStrategyList>
{page.map((strategy, index) => ( {page.map((strategy, index) => (
<li key={strategy.id}> <StyledListItem key={strategy.id}>
{index > 0 ||
releasePlans.length > 0 ? (
<StrategySeparator />
) : null}
<StrategyDraggableItem <StrategyDraggableItem
strategy={strategy} strategy={strategy}
index={ index={
index + index + pageIndex * pageSize
pageIndex * pageSize
} }
environmentName={ environmentName={
featureEnvironment.name featureEnvironment.name
@ -315,17 +302,12 @@ export const EnvironmentAccordionBody = ({
onDragStartRef={ onDragStartRef={
(() => {}) as any (() => {}) as any
} }
onDragOver={ onDragOver={(() => {}) as any}
(() => {}) as any onDragEnd={(() => {}) as any}
}
onDragEnd={
(() => {}) as any
}
/> />
</li> </StyledListItem>
))} ))}
</StyledStrategyList> </StyledContentList>
<br />
<Pagination <Pagination
count={pages.length} count={pages.length}
shape='rounded' shape='rounded'
@ -334,20 +316,17 @@ export const EnvironmentAccordionBody = ({
setPageIndex(page - 1) setPageIndex(page - 1)
} }
/> />
</> </PaginatedStrategyContainer>
} )}
/> </StyledContentList>
</> ) : (
}
elseShow={
<FeatureStrategyEmpty <FeatureStrategyEmpty
projectId={projectId} projectId={projectId}
featureId={featureId} featureId={featureId}
environmentId={featureEnvironment.name} environmentId={featureEnvironment.name}
/> />
} )}
/>
</StyledAccordionBodyInnerContainer> </StyledAccordionBodyInnerContainer>
</StyledAccordionBody> </div>
); );
}; };

View File

@ -15,8 +15,7 @@ import {
type ScheduledChangeRequestViewModel, type ScheduledChangeRequestViewModel,
useScheduledChangeRequestsWithStrategy, useScheduledChangeRequestsWithStrategy,
} from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy'; } from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy';
import { StrategySeparator as NewStrategySeparator } from 'component/common/StrategySeparator/StrategySeparator'; import { StrategyItem } from './StrategyItem/StrategyItem';
import { StrategyItem as NewStrategyItem } from './StrategyItem/StrategyItem';
interface IStrategyDraggableItemProps { interface IStrategyDraggableItemProps {
strategy: IFeatureStrategy; strategy: IFeatureStrategy;
@ -65,12 +64,7 @@ export const StrategyDraggableItem = ({
onDragOver={onDragOver(ref, index)} onDragOver={onDragOver(ref, index)}
sx={{ opacity: isDragging ? '0.5' : '1' }} sx={{ opacity: isDragging ? '0.5' : '1' }}
> >
<ConditionallyRender <StrategyItem
condition={index > 0}
show={<NewStrategySeparator text='OR' />}
/>
<NewStrategyItem
strategy={strategy} strategy={strategy}
environmentId={environmentName} environmentId={environmentName}
otherEnvironments={otherEnvironments} otherEnvironments={otherEnvironments}

View File

@ -46,7 +46,6 @@ const StyledAccordionFooter = styled('footer')(({ theme }) => ({
flexDirection: 'column', flexDirection: 'column',
alignItems: 'flex-end', alignItems: 'flex-end',
gap: theme.spacing(2), gap: theme.spacing(2),
borderTop: `1px solid ${theme.palette.divider}`,
})); }));
const StyledEnvironmentAccordionContainer = styled('div')(({ theme }) => ({ const StyledEnvironmentAccordionContainer = styled('div')(({ theme }) => ({

View File

@ -28,10 +28,11 @@ const StyledContainer = styled('div', {
shouldForwardProp: (prop) => prop !== 'readonly', shouldForwardProp: (prop) => prop !== 'readonly',
})<{ readonly?: boolean }>(({ theme, readonly }) => ({ })<{ readonly?: boolean }>(({ theme, readonly }) => ({
padding: theme.spacing(2), padding: theme.spacing(2),
'& + &': { paddingTop: theme.spacing(0),
marginTop: theme.spacing(2),
},
background: 'inherit', background: 'inherit',
display: 'flex',
flexFlow: 'column',
gap: theme.spacing(1),
})); }));
const StyledHeader = styled('div')(({ theme }) => ({ const StyledHeader = styled('div')(({ theme }) => ({
@ -68,7 +69,6 @@ const StyledHeaderDescription = styled('p')(({ theme }) => ({
const StyledBody = styled('div')(({ theme }) => ({ const StyledBody = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
marginTop: theme.spacing(3),
})); }));
const StyledConnection = styled('div')(({ theme }) => ({ const StyledConnection = styled('div')(({ theme }) => ({