1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-01 01:18:10 +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 {
text: 'AND' | 'OR';
}
const StyledAnd = styled('div')(({ theme }) => ({
const Chip = styled('div')(({ theme }) => ({
padding: theme.spacing(0.75, 1),
color: theme.palette.text.primary,
fontSize: theme.fontSizes.smallerBody,
backgroundColor: theme.palette.background.elevation2,
position: 'absolute',
zIndex: theme.zIndex.fab,
top: '50%',
left: theme.spacing(2),
top: 0,
transform: 'translateY(-50%)',
lineHeight: 1,
borderRadius: theme.shape.borderRadiusLarge,
}));
const StyledOr = styled(StyledAnd)(({ theme }) => ({
fontWeight: 'bold',
backgroundColor: theme.palette.background.alternative,
color: theme.palette.primary.contrastText,
left: theme.spacing(4),
}));
const StyledSeparator = styled('hr')(({ theme }) => ({
border: 0,
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>
);
export const StrategySeparator = () => {
return <Chip>OR</Chip>;
};

View File

@ -8,8 +8,6 @@ import { Alert, Pagination, styled } from '@mui/material';
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import useToast from 'hooks/useToast';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategyDraggableItem } from './StrategyDraggableItem/LegacyStrategyDraggableItem';
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
@ -22,10 +20,9 @@ import type { IFeatureStrategy } from 'interfaces/strategy';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag';
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
import { Badge } from 'component/common/Badge/Badge';
import { StrategyDraggableItem as NewStrategyDraggableItem } from './StrategyDraggableItem/StrategyDraggableItem';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import { StrategyDraggableItem } from './StrategyDraggableItem/StrategyDraggableItem';
import { ReleasePlan } from '../../../ReleasePlan/ReleasePlan';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
interface IEnvironmentAccordionBodyProps {
isDisabled: boolean;
@ -33,41 +30,43 @@ interface IEnvironmentAccordionBodyProps {
otherEnvironments?: IFeatureEnvironment['name'][];
}
const StyledAccordionBody = styled('div')(({ theme }) => ({
width: '100%',
position: 'relative',
paddingBottom: theme.spacing(2),
}));
const StyledAccordionBodyInnerContainer = styled('div')(({ theme }) => ({
[theme.breakpoints.down(400)]: {
padding: theme.spacing(1),
},
}));
const StyledBadge = styled(Badge)(({ theme }) => ({
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')({
const StyledContentList = styled('ol')({
listStyle: 'none',
padding: 0,
margin: 0,
});
const StyledReleasePlanList = styled(StyledStrategyList)(({ theme }) => ({
background: theme.palette.background.elevation2,
const StyledListItem = styled('li', {
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 = ({
@ -232,122 +231,102 @@ export const EnvironmentAccordionBody = ({
};
return (
<StyledAccordionBody>
<div>
<StyledAccordionBodyInnerContainer>
<ConditionallyRender
condition={releasePlans.length > 0 || strategies.length > 0}
show={
<>
<StyledReleasePlanList>
{releasePlans.map((plan) => (
<li key={plan.id}>
<ReleasePlan
plan={plan}
environmentIsDisabled={isDisabled}
/>
</li>
))}
</StyledReleasePlanList>
{releasePlans.length > 0 &&
strategies.length > 0 ? (
<StrategySeparator text='OR' />
) : null}
<ConditionallyRender
condition={
strategies.length < 50 ||
!manyStrategiesPagination
}
show={
<StyledStrategyList>
{strategies.map((strategy, index) => (
<li key={strategy.id}>
<NewStrategyDraggableItem
strategy={strategy}
index={index}
environmentName={
featureEnvironment.name
}
otherEnvironments={
otherEnvironments
}
isDragging={
dragItem?.id ===
strategy.id
}
onDragStartRef={
onDragStartRef
}
onDragOver={onDragOver(
strategy.id,
)}
onDragEnd={onDragEnd}
/>
</li>
))}
</StyledStrategyList>
}
elseShow={
<>
<Alert severity='error'>
We noticed you're using a high
number of activation strategies. To
ensure a more targeted approach,
consider leveraging constraints or
segments.
</Alert>
<br />
<StyledStrategyList>
{page.map((strategy, index) => (
<li key={strategy.id}>
<StrategyDraggableItem
strategy={strategy}
index={
index +
pageIndex * pageSize
}
environmentName={
featureEnvironment.name
}
otherEnvironments={
otherEnvironments
}
isDragging={false}
onDragStartRef={
(() => {}) as any
}
onDragOver={
(() => {}) as any
}
onDragEnd={
(() => {}) as any
}
/>
</li>
))}
</StyledStrategyList>
<br />
<Pagination
count={pages.length}
shape='rounded'
page={pageIndex + 1}
onChange={(_, page) =>
setPageIndex(page - 1)
{releasePlans.length > 0 || strategies.length > 0 ? (
<StyledContentList>
{releasePlans.map((plan) => (
<StyledListItem type='release plan' key={plan.id}>
<ReleasePlan
plan={plan}
environmentIsDisabled={isDisabled}
/>
</StyledListItem>
))}
{strategies.length < 50 || !manyStrategiesPagination ? (
<StyledContentList>
{strategies.map((strategy, index) => (
<StyledListItem key={strategy.id}>
{index > 0 ||
releasePlans.length > 0 ? (
<StrategySeparator />
) : null}
<StrategyDraggableItem
strategy={strategy}
index={index}
environmentName={
featureEnvironment.name
}
otherEnvironments={
otherEnvironments
}
isDragging={
dragItem?.id === strategy.id
}
onDragStartRef={onDragStartRef}
onDragOver={onDragOver(strategy.id)}
onDragEnd={onDragEnd}
/>
</>
}
/>
</>
}
elseShow={
<FeatureStrategyEmpty
projectId={projectId}
featureId={featureId}
environmentId={featureEnvironment.name}
/>
}
/>
</StyledListItem>
))}
</StyledContentList>
) : (
<PaginatedStrategyContainer>
<StyledAlert severity='warning'>
We noticed you're using a high number of
activation strategies. To ensure a more
targeted approach, consider leveraging
constraints or segments.
</StyledAlert>
<StyledContentList>
{page.map((strategy, index) => (
<StyledListItem key={strategy.id}>
{index > 0 ||
releasePlans.length > 0 ? (
<StrategySeparator />
) : null}
<StrategyDraggableItem
strategy={strategy}
index={
index + pageIndex * pageSize
}
environmentName={
featureEnvironment.name
}
otherEnvironments={
otherEnvironments
}
isDragging={false}
onDragStartRef={
(() => {}) as any
}
onDragOver={(() => {}) as any}
onDragEnd={(() => {}) as any}
/>
</StyledListItem>
))}
</StyledContentList>
<Pagination
count={pages.length}
shape='rounded'
page={pageIndex + 1}
onChange={(_, page) =>
setPageIndex(page - 1)
}
/>
</PaginatedStrategyContainer>
)}
</StyledContentList>
) : (
<FeatureStrategyEmpty
projectId={projectId}
featureId={featureId}
environmentId={featureEnvironment.name}
/>
)}
</StyledAccordionBodyInnerContainer>
</StyledAccordionBody>
</div>
);
};

View File

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

View File

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

View File

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