mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
refactor: batch of changes for styled components (#2791)
This commit is contained in:
parent
5fe16207db
commit
231b26995c
@ -1,9 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
label: {
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}));
|
@ -8,12 +8,12 @@ import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
import { UPDATE_FEATURE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions';
|
||||
import React from 'react';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useStyles } from './FeatureOverviewEnvSwitch.styles';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle';
|
||||
import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
|
||||
import { UpdateEnabledMessage } from '../../../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
interface IFeatureOverviewEnvSwitchProps {
|
||||
env: IFeatureEnvironment;
|
||||
@ -22,6 +22,12 @@ interface IFeatureOverviewEnvSwitchProps {
|
||||
showInfoBox: () => void;
|
||||
}
|
||||
|
||||
const StyledLabel = styled('label')({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
});
|
||||
|
||||
const FeatureOverviewEnvSwitch = ({
|
||||
env,
|
||||
callback,
|
||||
@ -34,7 +40,6 @@ const FeatureOverviewEnvSwitch = ({
|
||||
useFeatureApi();
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { classes: styles } = useStyles();
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
const {
|
||||
onChangeRequestToggle,
|
||||
@ -110,7 +115,7 @@ const FeatureOverviewEnvSwitch = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className={styles.label}>
|
||||
<StyledLabel>
|
||||
<PermissionSwitch
|
||||
permission={UPDATE_FEATURE_ENVIRONMENT}
|
||||
projectId={projectId}
|
||||
@ -119,7 +124,7 @@ const FeatureOverviewEnvSwitch = ({
|
||||
environmentId={env.name}
|
||||
/>
|
||||
{content}
|
||||
</label>
|
||||
</StyledLabel>
|
||||
<ChangeRequestDialogue
|
||||
isOpen={changeRequestDialogDetails.isOpen}
|
||||
onClose={onChangeRequestToggleClose}
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
accordionBodyInnerContainer: {
|
||||
[theme.breakpoints.down(400)]: {
|
||||
padding: '0.5rem',
|
||||
},
|
||||
},
|
||||
accordionBody: {
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
paddingBottom: '1rem',
|
||||
},
|
||||
}));
|
@ -1,5 +1,5 @@
|
||||
import { DragEventHandler, RefObject, useEffect, useState } from 'react';
|
||||
import { Alert } from '@mui/material';
|
||||
import { Alert, styled } from '@mui/material';
|
||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import useToast from 'hooks/useToast';
|
||||
@ -8,7 +8,6 @@ import { StrategyDraggableItem } from './StrategyDraggableItem/StrategyDraggable
|
||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useStyles } from './EnvironmentAccordionBody.styles';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
|
||||
interface IEnvironmentAccordionBodyProps {
|
||||
@ -17,6 +16,18 @@ 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 EnvironmentAccordionBody = ({
|
||||
featureEnvironment,
|
||||
isDisabled,
|
||||
@ -35,7 +46,6 @@ const EnvironmentAccordionBody = ({
|
||||
index: number;
|
||||
height: number;
|
||||
} | null>(null);
|
||||
const { classes: styles } = useStyles();
|
||||
useEffect(() => {
|
||||
// Use state to enable drag and drop, but switch to API output when it arrives
|
||||
setStrategies(featureEnvironment?.strategies || []);
|
||||
@ -128,8 +138,8 @@ const EnvironmentAccordionBody = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.accordionBody}>
|
||||
<div className={styles.accordionBodyInnerContainer}>
|
||||
<StyledAccordionBody>
|
||||
<StyledAccordionBodyInnerContainer>
|
||||
<ConditionallyRender
|
||||
condition={strategies.length > 0 && isDisabled}
|
||||
show={() => (
|
||||
@ -166,8 +176,8 @@ const EnvironmentAccordionBody = ({
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</StyledAccordionBodyInnerContainer>
|
||||
</StyledAccordionBody>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
width: '100%',
|
||||
padding: theme.spacing(2, 3),
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
chip: {
|
||||
margin: '0.25rem',
|
||||
},
|
||||
paragraph: {
|
||||
display: 'inline',
|
||||
margin: '0.25rem 0',
|
||||
maxWidth: '95%',
|
||||
textAlign: 'center',
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
}));
|
@ -1,6 +1,5 @@
|
||||
import { Chip } from '@mui/material';
|
||||
import { Chip, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useStyles } from './ConstraintItem.styles';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
|
||||
interface IConstraintItemProps {
|
||||
@ -8,22 +7,40 @@ interface IConstraintItemProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
width: '100%',
|
||||
padding: theme.spacing(2, 3),
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
}));
|
||||
|
||||
const StyledParagraph = styled('p')(({ theme }) => ({
|
||||
display: 'inline',
|
||||
margin: theme.spacing(0.5, 0),
|
||||
maxWidth: '95%',
|
||||
textAlign: 'center',
|
||||
wordBreak: 'break-word',
|
||||
}));
|
||||
|
||||
const StyledChip = styled(Chip)(({ theme }) => ({
|
||||
margin: theme.spacing(0.5),
|
||||
}));
|
||||
|
||||
export const ConstraintItem = ({ value, text }: IConstraintItemProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<StyledContainer>
|
||||
<ConditionallyRender
|
||||
condition={value.length === 0}
|
||||
show={<p>No {text}s added yet.</p>}
|
||||
elseShow={
|
||||
<div>
|
||||
<p className={styles.paragraph}>
|
||||
<StyledParagraph>
|
||||
{value.length}{' '}
|
||||
{value.length > 1 ? `${text}s` : text} will get
|
||||
access.
|
||||
</p>
|
||||
</StyledParagraph>
|
||||
{value.map((v: string) => (
|
||||
<Chip
|
||||
<StyledChip
|
||||
key={v}
|
||||
label={
|
||||
<StringTruncator
|
||||
@ -32,12 +49,11 @@ export const ConstraintItem = ({ value, text }: IConstraintItemProps) => {
|
||||
maxLength={50}
|
||||
/>
|
||||
}
|
||||
className={styles.chip}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,12 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
valueContainer: {
|
||||
padding: theme.spacing(2, 3),
|
||||
border: `1px solid ${theme.palette.dividerAlternative}`,
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
},
|
||||
valueSeparator: {
|
||||
color: theme.palette.grey[700],
|
||||
},
|
||||
}));
|
@ -1,6 +1,6 @@
|
||||
import { Fragment, useMemo, VFC } from 'react';
|
||||
import { Box, Chip } from '@mui/material';
|
||||
import { IFeatureStrategy, IFeatureStrategyPayload } from 'interfaces/strategy';
|
||||
import { Box, Chip, styled } from '@mui/material';
|
||||
import { IFeatureStrategyPayload } from 'interfaces/strategy';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||
@ -10,7 +10,6 @@ import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { FeatureOverviewSegment } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment';
|
||||
import { ConstraintAccordionList } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
|
||||
import { useStyles } from './StrategyExecution.styles';
|
||||
import {
|
||||
parseParameterNumber,
|
||||
parseParameterString,
|
||||
@ -28,11 +27,20 @@ const NoItems: VFC = () => (
|
||||
</Box>
|
||||
);
|
||||
|
||||
const StyledValueContainer = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(2, 3),
|
||||
border: `1px solid ${theme.palette.dividerAlternative}`,
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
}));
|
||||
|
||||
const StyledValueSeparator = styled('span')(({ theme }) => ({
|
||||
color: theme.palette.neutral.main,
|
||||
}));
|
||||
|
||||
export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
strategy,
|
||||
}) => {
|
||||
const { parameters, constraints = [] } = strategy;
|
||||
const { classes: styles } = useStyles();
|
||||
const { strategies } = useStrategies();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { segments } = useSegments();
|
||||
@ -54,8 +62,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
const percentage = parseParameterNumber(parameters[key]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={styles.valueContainer}
|
||||
<StyledValueContainer
|
||||
sx={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<Box sx={{ mr: 2 }}>
|
||||
@ -77,7 +84,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
: ''}{' '}
|
||||
is included.
|
||||
</div>
|
||||
</Box>
|
||||
</StyledValueContainer>
|
||||
);
|
||||
case 'userIds':
|
||||
case 'UserIds':
|
||||
@ -101,12 +108,12 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}, [parameters, definition, constraints, styles]);
|
||||
}, [parameters, definition, constraints]);
|
||||
|
||||
const customStrategyList = useMemo(() => {
|
||||
if (!parameters || !definition?.editable) return null;
|
||||
const isSetTo = (
|
||||
<span className={styles.valueSeparator}>{' is set to '}</span>
|
||||
<StyledValueSeparator>{' is set to '}</StyledValueSeparator>
|
||||
);
|
||||
|
||||
return definition?.parameters.map(param => {
|
||||
@ -123,9 +130,9 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
const values = parseParameterStrings(parameters[name]);
|
||||
|
||||
return values.length > 0 ? (
|
||||
<div className={styles.valueContainer}>
|
||||
<StyledValueContainer>
|
||||
{nameItem}{' '}
|
||||
<span className={styles.valueSeparator}>
|
||||
<StyledValueSeparator>
|
||||
has {values.length}{' '}
|
||||
{values.length > 1 ? `items` : 'item'}:{' '}
|
||||
{values.map((item: string) => (
|
||||
@ -141,15 +148,14 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
sx={{ mr: 0.5 }}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
</StyledValueSeparator>
|
||||
</StyledValueContainer>
|
||||
) : null;
|
||||
|
||||
case 'percentage':
|
||||
const percentage = parseParameterNumber(parameters[name]);
|
||||
return parameters[name] !== '' ? (
|
||||
<Box
|
||||
className={styles.valueContainer}
|
||||
<StyledValueContainer
|
||||
sx={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<Box sx={{ mr: 2 }}>
|
||||
@ -168,13 +174,13 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
label={`${percentage}%`}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
</StyledValueContainer>
|
||||
) : null;
|
||||
|
||||
case 'boolean':
|
||||
return parameters[name] === 'true' ||
|
||||
parameters[name] === 'false' ? (
|
||||
<div className={styles.valueContainer}>
|
||||
<StyledValueContainer>
|
||||
<StringTruncator
|
||||
maxLength={15}
|
||||
maxWidth="150"
|
||||
@ -191,20 +197,20 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
size="small"
|
||||
label={parameters[name]}
|
||||
/>
|
||||
</div>
|
||||
</StyledValueContainer>
|
||||
) : null;
|
||||
|
||||
case 'string':
|
||||
const value = parseParameterString(parameters[name]);
|
||||
return typeof parameters[name] !== 'undefined' ? (
|
||||
<div className={styles.valueContainer}>
|
||||
<StyledValueContainer>
|
||||
{nameItem}
|
||||
<ConditionallyRender
|
||||
condition={value === ''}
|
||||
show={
|
||||
<span className={styles.valueSeparator}>
|
||||
<StyledValueSeparator>
|
||||
{' is an empty string'}
|
||||
</span>
|
||||
</StyledValueSeparator>
|
||||
}
|
||||
elseShow={
|
||||
<>
|
||||
@ -217,13 +223,13 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</StyledValueContainer>
|
||||
) : null;
|
||||
|
||||
case 'number':
|
||||
const number = parseParameterNumber(parameters[name]);
|
||||
return parameters[name] !== '' && number !== undefined ? (
|
||||
<div className={styles.valueContainer}>
|
||||
<StyledValueContainer>
|
||||
{nameItem}
|
||||
{isSetTo}
|
||||
<StringTruncator
|
||||
@ -231,7 +237,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
text={String(number)}
|
||||
maxLength={50}
|
||||
/>
|
||||
</div>
|
||||
</StyledValueContainer>
|
||||
) : null;
|
||||
case 'default':
|
||||
return null;
|
||||
@ -239,7 +245,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
|
||||
return null;
|
||||
});
|
||||
}, [parameters, definition, styles]);
|
||||
}, [parameters, definition]);
|
||||
|
||||
if (!parameters) {
|
||||
return <NoItems />;
|
||||
@ -259,7 +265,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
),
|
||||
strategy.name === 'default' && (
|
||||
<>
|
||||
<Box sx={{ width: '100%' }} className={styles.valueContainer}>
|
||||
<StyledValueContainer sx={{ width: '100%' }}>
|
||||
The standard strategy is{' '}
|
||||
<Chip
|
||||
variant="outlined"
|
||||
@ -268,7 +274,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
label="ON"
|
||||
/>{' '}
|
||||
for all users.
|
||||
</Box>
|
||||
</StyledValueContainer>
|
||||
</>
|
||||
),
|
||||
...(parametersList ?? []),
|
||||
|
@ -1,98 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
featureOverviewEnvironment: {
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
marginBottom: theme.spacing(2),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
accordion: {
|
||||
boxShadow: 'none',
|
||||
background: 'none',
|
||||
},
|
||||
accordionHeader: {
|
||||
boxShadow: 'none',
|
||||
padding: '1rem 2rem',
|
||||
[theme.breakpoints.down(400)]: {
|
||||
padding: '0.5rem 1rem',
|
||||
},
|
||||
},
|
||||
accordionBodyInnerContainer: {
|
||||
[theme.breakpoints.down(400)]: {
|
||||
padding: '0.5rem',
|
||||
},
|
||||
},
|
||||
accordionDetails: {
|
||||
padding: theme.spacing(3),
|
||||
background: theme.palette.secondaryContainer,
|
||||
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottom: `4px solid ${theme.palette.primary.light}`,
|
||||
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(2, 1),
|
||||
},
|
||||
},
|
||||
accordionDetailsDisabled: {
|
||||
borderBottom: `4px solid ${theme.palette.neutral.border}`,
|
||||
},
|
||||
accordionBody: {
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
paddingBottom: theme.spacing(2),
|
||||
},
|
||||
header: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
headerTitle: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
[theme.breakpoints.down(560)]: {
|
||||
flexDirection: 'column',
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
headerIcon: {
|
||||
[theme.breakpoints.down(560)]: {
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
},
|
||||
iconContainer: {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
borderRadius: '50%',
|
||||
width: '28px',
|
||||
height: '28px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: '0.5rem',
|
||||
},
|
||||
icon: {
|
||||
fill: '#fff',
|
||||
width: '17px',
|
||||
height: '17px',
|
||||
},
|
||||
linkContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
marginBottom: '1rem',
|
||||
},
|
||||
truncator: {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
[theme.breakpoints.down(560)]: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: '1.8rem',
|
||||
[theme.breakpoints.down(560)]: {
|
||||
flexDirection: 'column',
|
||||
marginLeft: '0',
|
||||
},
|
||||
},
|
||||
}));
|
@ -4,9 +4,8 @@ import {
|
||||
AccordionSummary,
|
||||
Box,
|
||||
Chip,
|
||||
useTheme,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import classNames from 'classnames';
|
||||
import { ExpandMore } from '@mui/icons-material';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
|
||||
@ -15,7 +14,6 @@ import { getFeatureMetrics } from 'utils/getFeatureMetrics';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
import { useStyles } from './FeatureOverviewEnvironment.styles';
|
||||
import EnvironmentAccordionBody from './EnvironmentAccordionBody/EnvironmentAccordionBody';
|
||||
import { EnvironmentFooter } from './EnvironmentFooter/EnvironmentFooter';
|
||||
import FeatureOverviewEnvironmentMetrics from './FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
|
||||
@ -23,17 +21,104 @@ import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureSt
|
||||
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons';
|
||||
// import { Badge } from 'component/common/Badge/Badge';
|
||||
|
||||
interface IFeatureOverviewEnvironmentProps {
|
||||
env: IFeatureEnvironment;
|
||||
}
|
||||
|
||||
const StyledFeatureOverviewEnvironment = styled('div', {
|
||||
shouldForwardProp: prop => prop !== 'enabled',
|
||||
})<{ enabled: boolean }>(({ theme, enabled }) => ({
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
marginBottom: theme.spacing(2),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
background: enabled
|
||||
? theme.palette.background.paper
|
||||
: theme.palette.neutral.light,
|
||||
}));
|
||||
|
||||
const StyledAccordion = styled(Accordion)({
|
||||
boxShadow: 'none',
|
||||
background: 'none',
|
||||
});
|
||||
|
||||
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
|
||||
boxShadow: 'none',
|
||||
padding: theme.spacing(2, 4),
|
||||
[theme.breakpoints.down(400)]: {
|
||||
padding: theme.spacing(1, 2),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledAccordionDetails = styled(AccordionDetails, {
|
||||
shouldForwardProp: prop => prop !== 'enabled',
|
||||
})<{ enabled: boolean }>(({ theme, enabled }) => ({
|
||||
padding: theme.spacing(3),
|
||||
background: theme.palette.secondaryContainer,
|
||||
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
||||
borderBottom: `4px solid ${
|
||||
enabled ? theme.palette.primary.light : theme.palette.neutral.border
|
||||
}`,
|
||||
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(2, 1),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledEnvironmentAccordionBody = styled(EnvironmentAccordionBody)(
|
||||
({ theme }) => ({
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
paddingBottom: theme.spacing(2),
|
||||
})
|
||||
);
|
||||
|
||||
const StyledHeader = styled('div', {
|
||||
shouldForwardProp: prop => prop !== 'enabled',
|
||||
})<{ enabled: boolean }>(({ theme, enabled }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
color: enabled ? theme.palette.text.primary : theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
const StyledHeaderTitle = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
[theme.breakpoints.down(560)]: {
|
||||
flexDirection: 'column',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledEnvironmentIcon = styled(EnvironmentIcon)(({ theme }) => ({
|
||||
[theme.breakpoints.down(560)]: {
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledStringTruncator = styled(StringTruncator)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
[theme.breakpoints.down(560)]: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: '1.8rem',
|
||||
[theme.breakpoints.down(560)]: {
|
||||
flexDirection: 'column',
|
||||
marginLeft: '0',
|
||||
},
|
||||
}));
|
||||
|
||||
const FeatureOverviewEnvironment = ({
|
||||
env,
|
||||
}: IFeatureOverviewEnvironmentProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const theme = useTheme();
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { metrics } = useFeatureMetrics(projectId, featureId);
|
||||
@ -48,40 +133,19 @@ const FeatureOverviewEnvironment = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.featureOverviewEnvironment}
|
||||
style={{
|
||||
background: !env.enabled
|
||||
? theme.palette.neutral.light
|
||||
: theme.palette.background.paper,
|
||||
}}
|
||||
>
|
||||
<Accordion
|
||||
className={styles.accordion}
|
||||
<StyledFeatureOverviewEnvironment enabled={env.enabled}>
|
||||
<StyledAccordion
|
||||
data-testid={`${FEATURE_ENVIRONMENT_ACCORDION}_${env.name}`}
|
||||
>
|
||||
<AccordionSummary
|
||||
className={styles.accordionHeader}
|
||||
<StyledAccordionSummary
|
||||
expandIcon={<ExpandMore titleAccess="Toggle" />}
|
||||
>
|
||||
<div
|
||||
className={styles.header}
|
||||
data-loading
|
||||
style={{
|
||||
color: !env.enabled
|
||||
? theme.palette.text.secondary
|
||||
: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
<div className={styles.headerTitle}>
|
||||
<EnvironmentIcon
|
||||
enabled={env.enabled}
|
||||
className={styles.headerIcon}
|
||||
/>
|
||||
<StyledHeader data-loading enabled={env.enabled}>
|
||||
<StyledHeaderTitle>
|
||||
<StyledEnvironmentIcon enabled={env.enabled} />
|
||||
<div>
|
||||
<StringTruncator
|
||||
<StyledStringTruncator
|
||||
text={env.name}
|
||||
className={styles.truncator}
|
||||
maxWidth="100"
|
||||
maxLength={15}
|
||||
/>
|
||||
@ -97,8 +161,8 @@ const FeatureOverviewEnvironment = ({
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.container}>
|
||||
</StyledHeaderTitle>
|
||||
<StyledContainer>
|
||||
<FeatureStrategyMenu
|
||||
label="Add strategy"
|
||||
projectId={projectId}
|
||||
@ -109,21 +173,17 @@ const FeatureOverviewEnvironment = ({
|
||||
<FeatureStrategyIcons
|
||||
strategies={featureEnvironment?.strategies}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
</StyledHeader>
|
||||
|
||||
<FeatureOverviewEnvironmentMetrics
|
||||
environmentMetric={environmentMetric}
|
||||
disabled={!env.enabled}
|
||||
/>
|
||||
</AccordionSummary>
|
||||
</StyledAccordionSummary>
|
||||
|
||||
<AccordionDetails
|
||||
className={classNames(styles.accordionDetails, {
|
||||
[styles.accordionDetailsDisabled]: !env.enabled,
|
||||
})}
|
||||
>
|
||||
<EnvironmentAccordionBody
|
||||
<StyledAccordionDetails enabled={env.enabled}>
|
||||
<StyledEnvironmentAccordionBody
|
||||
featureEnvironment={featureEnvironment}
|
||||
isDisabled={!env.enabled}
|
||||
otherEnvironments={feature?.environments
|
||||
@ -156,9 +216,9 @@ const FeatureOverviewEnvironment = ({
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</div>
|
||||
</StyledAccordionDetails>
|
||||
</StyledAccordion>
|
||||
</StyledFeatureOverviewEnvironment>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,42 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
marginLeft: 'auto',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
info: {
|
||||
marginRight: '0.5rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
icon: {
|
||||
fill: theme.palette.grey[300],
|
||||
height: '75px',
|
||||
width: '75px',
|
||||
[theme.breakpoints.down(500)]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
infoParagraph: {
|
||||
maxWidth: '270px',
|
||||
marginTop: '0.25rem',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
textAlign: 'right',
|
||||
[theme.breakpoints.down(700)]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
percentage: {
|
||||
color: theme.palette.primary.main,
|
||||
textAlign: 'right',
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
},
|
||||
percentageCircle: {
|
||||
margin: '0 1rem',
|
||||
[theme.breakpoints.down(500)]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
}));
|
@ -3,19 +3,62 @@ import { useTheme } from '@mui/system';
|
||||
import { IFeatureEnvironmentMetrics } from 'interfaces/featureToggle';
|
||||
import { calculatePercentage } from 'utils/calculatePercentage';
|
||||
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
||||
import { useStyles } from './FeatureOverviewEnvironmentMetrics.styles';
|
||||
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
interface IFeatureOverviewEnvironmentMetrics {
|
||||
environmentMetric?: IFeatureEnvironmentMetrics;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('div')({
|
||||
marginLeft: 'auto',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const StyledInfo = styled('div')(({ theme }) => ({
|
||||
marginRight: theme.spacing(1),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
|
||||
const StyledPercentage = styled('p')(({ theme }) => ({
|
||||
color: theme.palette.primary.main,
|
||||
textAlign: 'right',
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
}));
|
||||
|
||||
const StyledInfoParagraph = styled('p')(({ theme }) => ({
|
||||
maxWidth: '270px',
|
||||
marginTop: theme.spacing(0.5),
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
textAlign: 'right',
|
||||
[theme.breakpoints.down(700)]: {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledIcon = styled(FiberManualRecord)(({ theme }) => ({
|
||||
fill: theme.palette.standaloneBackground,
|
||||
height: '75px',
|
||||
width: '75px',
|
||||
[theme.breakpoints.down(500)]: {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledPercentageCircle = styled('div')(({ theme }) => ({
|
||||
margin: theme.spacing(0, 2),
|
||||
[theme.breakpoints.down(500)]: {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const FeatureOverviewEnvironmentMetrics = ({
|
||||
environmentMetric,
|
||||
disabled = false,
|
||||
}: IFeatureOverviewEnvironmentMetrics) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
if (!environmentMetric) return null;
|
||||
@ -28,10 +71,9 @@ const FeatureOverviewEnvironmentMetrics = ({
|
||||
(environmentMetric.yes === 0 && environmentMetric.no === 0)
|
||||
) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.info}>
|
||||
<p
|
||||
className={styles.percentage}
|
||||
<StyledContainer>
|
||||
<StyledInfo>
|
||||
<StyledPercentage
|
||||
style={{
|
||||
color: disabled
|
||||
? theme.palette.text.secondary
|
||||
@ -40,9 +82,8 @@ const FeatureOverviewEnvironmentMetrics = ({
|
||||
data-loading
|
||||
>
|
||||
{percentage}%
|
||||
</p>
|
||||
<p
|
||||
className={styles.infoParagraph}
|
||||
</StyledPercentage>
|
||||
<StyledInfoParagraph
|
||||
style={{
|
||||
color: disabled
|
||||
? theme.palette.text.secondary
|
||||
@ -53,22 +94,18 @@ const FeatureOverviewEnvironmentMetrics = ({
|
||||
The feature has been requested <b>0 times</b> and
|
||||
exposed
|
||||
<b> 0 times</b> in the last hour
|
||||
</p>
|
||||
</div>
|
||||
<FiberManualRecord
|
||||
className={styles.icon}
|
||||
style={{ transform: 'scale(1.1)' }}
|
||||
data-loading
|
||||
/>
|
||||
</div>
|
||||
</StyledInfoParagraph>
|
||||
</StyledInfo>
|
||||
<StyledIcon style={{ transform: 'scale(1.1)' }} data-loading />
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.info}>
|
||||
<p className={styles.percentage}>{percentage}%</p>
|
||||
<p className={styles.infoParagraph}>
|
||||
<StyledContainer>
|
||||
<StyledInfo>
|
||||
<StyledPercentage>{percentage}%</StyledPercentage>
|
||||
<StyledInfoParagraph>
|
||||
The feature has been requested{' '}
|
||||
<b>
|
||||
<PrettifyLargeNumber value={total} /> times
|
||||
@ -79,12 +116,12 @@ const FeatureOverviewEnvironmentMetrics = ({
|
||||
times
|
||||
</b>{' '}
|
||||
in the last hour
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.percentageCircle} data-loading>
|
||||
</StyledInfoParagraph>
|
||||
</StyledInfo>
|
||||
<StyledPercentageCircle data-loading>
|
||||
<PercentageCircle percentage={percentage} size="3rem" />
|
||||
</div>
|
||||
</div>
|
||||
</StyledPercentageCircle>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { capitalize } from '@mui/material';
|
||||
import classnames from 'classnames';
|
||||
import { capitalize, styled } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useStyles } from './FeatureOverviewMetadata.styles';
|
||||
import { Edit } from '@mui/icons-material';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
@ -13,9 +11,62 @@ import FeatureOverviewTags from './FeatureOverviewTags/FeatureOverviewTags';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
color: theme.palette.text.tertiaryContrast,
|
||||
backgroundColor: theme.palette.featureMetaData,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
maxWidth: '350px',
|
||||
minWidth: '350px',
|
||||
marginRight: theme.spacing(2),
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
width: '100%',
|
||||
maxWidth: 'none',
|
||||
minWidth: 'auto',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledPaddingContainerTop = styled('div')({
|
||||
padding: '1.5rem 1.5rem 0 1.5rem',
|
||||
});
|
||||
|
||||
const StyledPaddingContainerBottom = styled('div')(({ theme }) => ({
|
||||
padding: '0 1.5rem 1.5rem 1.5rem',
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
}));
|
||||
|
||||
const StyledMetaDataHeader = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const StyledHeader = styled('h2')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: 'normal',
|
||||
margin: 0,
|
||||
}));
|
||||
|
||||
const StyledBody = styled('div')(({ theme }) => ({
|
||||
margin: theme.spacing(2, 0),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
|
||||
const StyledBodyItem = styled('span')(({ theme }) => ({
|
||||
margin: theme.spacing(1, 0),
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
wordBreak: 'break-all',
|
||||
}));
|
||||
|
||||
const StyledDescriptionContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: theme.palette.text.tertiaryContrast,
|
||||
}));
|
||||
|
||||
const FeatureOverviewMetaData = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { classes: styles } = useStyles();
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { tags } = useTags(featureId);
|
||||
@ -25,24 +76,29 @@ const FeatureOverviewMetaData = () => {
|
||||
const IconComponent = getFeatureTypeIcons(type);
|
||||
|
||||
return (
|
||||
<div className={classnames(styles.container)}>
|
||||
<div className={styles.paddingContainerTop}>
|
||||
<div className={styles.metaDataHeader} data-loading>
|
||||
<IconComponent className={styles.headerIcon} />{' '}
|
||||
<h2 className={styles.header}>
|
||||
{capitalize(type || '')} toggle
|
||||
</h2>
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
<span className={styles.bodyItem} data-loading>
|
||||
<StyledContainer>
|
||||
<StyledPaddingContainerTop>
|
||||
<StyledMetaDataHeader data-loading>
|
||||
<IconComponent
|
||||
sx={theme => ({
|
||||
marginRight: theme.spacing(2),
|
||||
height: '40px',
|
||||
width: '40px',
|
||||
fill: theme.palette.text.tertiaryContrast,
|
||||
})}
|
||||
/>{' '}
|
||||
<StyledHeader>{capitalize(type || '')} toggle</StyledHeader>
|
||||
</StyledMetaDataHeader>
|
||||
<StyledBody>
|
||||
<StyledBodyItem data-loading>
|
||||
Project: {project}
|
||||
</span>
|
||||
</StyledBodyItem>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(description)}
|
||||
show={
|
||||
<span className={styles.bodyItem} data-loading>
|
||||
<StyledBodyItem data-loading>
|
||||
<div>Description:</div>
|
||||
<div className={styles.descriptionContainer}>
|
||||
<StyledDescriptionContainer>
|
||||
<p>{description}</p>
|
||||
<PermissionIconButton
|
||||
projectId={projectId}
|
||||
@ -53,14 +109,19 @@ const FeatureOverviewMetaData = () => {
|
||||
title: 'Edit description',
|
||||
}}
|
||||
>
|
||||
<Edit className={styles.editIcon} />
|
||||
<Edit
|
||||
sx={theme => ({
|
||||
color: theme.palette.text
|
||||
.tertiaryContrast,
|
||||
})}
|
||||
/>
|
||||
</PermissionIconButton>
|
||||
</div>
|
||||
</span>
|
||||
</StyledDescriptionContainer>
|
||||
</StyledBodyItem>
|
||||
}
|
||||
elseShow={
|
||||
<span data-loading>
|
||||
<div className={styles.descriptionContainer}>
|
||||
<StyledDescriptionContainer>
|
||||
No description.{' '}
|
||||
<PermissionIconButton
|
||||
projectId={projectId}
|
||||
@ -71,26 +132,31 @@ const FeatureOverviewMetaData = () => {
|
||||
title: 'Edit description',
|
||||
}}
|
||||
>
|
||||
<Edit className={styles.editIcon} />
|
||||
<Edit
|
||||
sx={theme => ({
|
||||
color: theme.palette.text
|
||||
.tertiaryContrast,
|
||||
})}
|
||||
/>
|
||||
</PermissionIconButton>
|
||||
</div>
|
||||
</StyledDescriptionContainer>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</StyledBody>
|
||||
</StyledPaddingContainerTop>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
tags.length > 0 &&
|
||||
!Boolean(uiConfig.flags.variantsPerEnvironment)
|
||||
}
|
||||
show={
|
||||
<div className={styles.paddingContainerBottom}>
|
||||
<StyledPaddingContainerBottom>
|
||||
<FeatureOverviewTags projectId={projectId} />
|
||||
</div>
|
||||
</StyledPaddingContainerBottom>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
color: '#fff',
|
||||
backgroundColor: theme.palette.featureMetaData,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
maxWidth: '350px',
|
||||
minWidth: '350px',
|
||||
marginRight: '1rem',
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
width: '100%',
|
||||
maxWidth: 'none',
|
||||
minWidth: 'auto',
|
||||
},
|
||||
},
|
||||
paddingContainerTop: {
|
||||
padding: '1.5rem 1.5rem 0 1.5rem',
|
||||
},
|
||||
paddingContainerBottom: {
|
||||
padding: '0 1.5rem 1.5rem 1.5rem',
|
||||
borderTop: `1px solid ${theme.palette.grey[300]}`,
|
||||
},
|
||||
metaDataHeader: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
header: {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: 'normal',
|
||||
margin: 0,
|
||||
},
|
||||
body: {
|
||||
margin: '1rem 0',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
bodyItem: {
|
||||
margin: '0.5rem 0',
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
headerIcon: {
|
||||
marginRight: '1rem',
|
||||
height: '40px',
|
||||
width: '40px',
|
||||
fill: '#fff',
|
||||
},
|
||||
descriptionContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: '#fff',
|
||||
},
|
||||
editIcon: {
|
||||
color: '#fff',
|
||||
},
|
||||
}));
|
@ -1,38 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginRight: '1rem',
|
||||
marginTop: '1rem',
|
||||
[theme.breakpoints.down(800)]: {
|
||||
width: '100%',
|
||||
maxWidth: 'none',
|
||||
},
|
||||
},
|
||||
tagHeader: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
tag: {
|
||||
height: '40px',
|
||||
width: '40px',
|
||||
fill: theme.palette.primary.main,
|
||||
marginRight: '0.8rem',
|
||||
},
|
||||
tagChip: {
|
||||
marginRight: '0.25rem',
|
||||
marginTop: '0.5rem',
|
||||
backgroundColor: '#fff',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
},
|
||||
closeIcon: {
|
||||
color: theme.palette.primary.light,
|
||||
'&:hover': {
|
||||
color: theme.palette.primary.light,
|
||||
},
|
||||
},
|
||||
}));
|
@ -1,8 +1,7 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Chip } from '@mui/material';
|
||||
import { Chip, styled } from '@mui/material';
|
||||
import { Close, Label } from '@mui/icons-material';
|
||||
import useTags from 'hooks/api/getters/useTags/useTags';
|
||||
import { useStyles } from './FeatureOverviewTags.styles';
|
||||
import slackIcon from 'assets/icons/slack.svg';
|
||||
import jiraIcon from 'assets/icons/jira.svg';
|
||||
import webhookIcon from 'assets/icons/webhooks.svg';
|
||||
@ -18,10 +17,37 @@ import AccessContext from 'contexts/AccessContext';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
|
||||
interface IFeatureOverviewTagsProps extends React.HTMLProps<HTMLDivElement> {
|
||||
interface IFeatureOverviewTagsProps {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginRight: theme.spacing(2),
|
||||
marginTop: theme.spacing(2),
|
||||
[theme.breakpoints.down(800)]: {
|
||||
width: '100%',
|
||||
maxWidth: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledTagChip = styled(Chip)(({ theme }) => ({
|
||||
marginRight: theme.spacing(0.5),
|
||||
marginTop: theme.spacing(1),
|
||||
backgroundColor: theme.palette.text.tertiaryContrast,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
const StyledCloseIcon = styled(Close)(({ theme }) => ({
|
||||
color: theme.palette.primary.light,
|
||||
'&:hover': {
|
||||
color: theme.palette.primary.light,
|
||||
},
|
||||
}));
|
||||
|
||||
const FeatureOverviewTags: React.FC<IFeatureOverviewTagsProps> = ({
|
||||
projectId,
|
||||
...rest
|
||||
@ -31,7 +57,6 @@ const FeatureOverviewTags: React.FC<IFeatureOverviewTagsProps> = ({
|
||||
value: '',
|
||||
type: '',
|
||||
});
|
||||
const { classes: styles } = useStyles();
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { tags, refetch } = useTags(featureId);
|
||||
const { tagTypes } = useTagTypes();
|
||||
@ -98,15 +123,12 @@ const FeatureOverviewTags: React.FC<IFeatureOverviewTagsProps> = ({
|
||||
};
|
||||
|
||||
const renderTag = (t: ITag) => (
|
||||
<Chip
|
||||
<StyledTagChip
|
||||
icon={tagIcon(t.type)}
|
||||
className={styles.tagChip}
|
||||
data-loading
|
||||
label={t.value}
|
||||
key={`${t.type}:${t.value}`}
|
||||
deleteIcon={
|
||||
<Close className={styles.closeIcon} titleAccess="Remove" />
|
||||
}
|
||||
deleteIcon={<StyledCloseIcon titleAccess="Remove" />}
|
||||
onDelete={
|
||||
canDeleteTag
|
||||
? () => {
|
||||
@ -119,7 +141,7 @@ const FeatureOverviewTags: React.FC<IFeatureOverviewTagsProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.container} {...rest}>
|
||||
<StyledContainer {...rest}>
|
||||
<Dialogue
|
||||
open={showDelDialog}
|
||||
onClose={() => {
|
||||
@ -141,7 +163,7 @@ const FeatureOverviewTags: React.FC<IFeatureOverviewTagsProps> = ({
|
||||
elseShow={<p data-loading>No tags to display</p>}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,30 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
innerContainer: {
|
||||
display: 'flex',
|
||||
},
|
||||
bodyContainer: {
|
||||
padding: 0,
|
||||
},
|
||||
listContainer: {
|
||||
width: '20%',
|
||||
borderRight: `1px solid ${theme.palette.grey[300]}`,
|
||||
padding: '1rem 0',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: '35%',
|
||||
},
|
||||
},
|
||||
listItem: {
|
||||
padding: '0.75rem 2rem',
|
||||
},
|
||||
innerBodyContainer: {
|
||||
padding: '2rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: 400,
|
||||
['& > *']: {
|
||||
margin: '0.5rem 0',
|
||||
},
|
||||
},
|
||||
}));
|
@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { useStyles } from './FeatureSettings.styles';
|
||||
import { List, ListItem } from '@mui/material';
|
||||
import { Box, List, ListItem, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import FeatureSettingsProject from './FeatureSettingsProject/FeatureSettingsProject';
|
||||
import { FeatureSettingsInformation } from './FeatureSettingsInformation/FeatureSettingsInformation';
|
||||
@ -11,21 +10,39 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
const METADATA = 'metadata';
|
||||
const PROJECT = 'project';
|
||||
|
||||
const StyledListContainer = styled('div')(({ theme }) => ({
|
||||
width: '20%',
|
||||
borderRight: `1px solid ${theme.palette.divider}`,
|
||||
padding: theme.spacing(2, 0),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: '35%',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledInnerBodyContainer = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(4),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: 400,
|
||||
['& > *']: {
|
||||
margin: theme.spacing(1, 0),
|
||||
},
|
||||
}));
|
||||
|
||||
export const FeatureSettings = () => {
|
||||
const { classes: styles } = useStyles();
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const [settings, setSettings] = useState(METADATA);
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
return (
|
||||
<PageContent header="Settings" bodyClass={styles.bodyContainer}>
|
||||
<div className={styles.innerContainer}>
|
||||
<div className={styles.listContainer}>
|
||||
<PageContent header="Settings" sx={{ padding: 0 }}>
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<StyledListContainer>
|
||||
<List>
|
||||
<ListItem
|
||||
key={0}
|
||||
className={styles.listItem}
|
||||
sx={{ padding: '0.75rem 2rem' }}
|
||||
button
|
||||
onClick={() => setSettings(METADATA)}
|
||||
selected={settings === METADATA}
|
||||
@ -34,7 +51,7 @@ export const FeatureSettings = () => {
|
||||
</ListItem>
|
||||
<ListItem
|
||||
key={1}
|
||||
className={styles.listItem}
|
||||
sx={{ padding: '0.75rem 2rem' }}
|
||||
button
|
||||
onClick={() => setSettings(PROJECT)}
|
||||
selected={settings === PROJECT}
|
||||
@ -43,8 +60,8 @@ export const FeatureSettings = () => {
|
||||
Project
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
<div className={styles.innerBodyContainer}>
|
||||
</StyledListContainer>
|
||||
<StyledInnerBodyContainer>
|
||||
<ConditionallyRender
|
||||
condition={settings === METADATA}
|
||||
show={
|
||||
@ -58,8 +75,8 @@ export const FeatureSettings = () => {
|
||||
condition={settings === PROJECT && uiConfig.flags.P}
|
||||
show={<FeatureSettingsProject />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</StyledInnerBodyContainer>
|
||||
</Box>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user