mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
Styled components batch4.1 (#2812)
Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
This commit is contained in:
parent
94c90b7731
commit
674e36b40b
@ -1,37 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
display: 'grid',
|
||||
lineHeight: 1.25,
|
||||
gridTemplateColumns: '1fr auto',
|
||||
alignSelf: 'start',
|
||||
alignItems: 'start',
|
||||
gap: '0.5rem',
|
||||
padding: '0.5rem',
|
||||
background: theme.palette.grey[200],
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
label: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
},
|
||||
description: {
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.grey[700],
|
||||
},
|
||||
button: {
|
||||
all: 'unset',
|
||||
lineHeight: 0.1,
|
||||
paddingTop: 1,
|
||||
display: 'block',
|
||||
cursor: 'pointer',
|
||||
'& svg': {
|
||||
fontSize: '1rem',
|
||||
opacity: 0.5,
|
||||
},
|
||||
'&:hover svg, &:focus-visible svg': {
|
||||
opacity: 0.75,
|
||||
},
|
||||
},
|
||||
}));
|
@ -1,6 +1,6 @@
|
||||
import { useStyles } from 'component/context/ContectFormChip/ContextFormChip.styles';
|
||||
import { Cancel } from '@mui/icons-material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
interface IContextFormChipProps {
|
||||
label: string;
|
||||
@ -8,27 +8,62 @@ interface IContextFormChipProps {
|
||||
onRemove: () => void;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('li')(({ theme }) => ({
|
||||
display: 'grid',
|
||||
lineHeight: 1.25,
|
||||
gridTemplateColumns: '1fr auto',
|
||||
alignSelf: 'start',
|
||||
alignItems: 'start',
|
||||
gap: theme.spacing(1),
|
||||
padding: theme.spacing(1),
|
||||
background: theme.palette.secondaryContainer,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
wordBreak: 'break-word',
|
||||
}));
|
||||
|
||||
const StyledLabel = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
const StyledDescription = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.neutral.main,
|
||||
}));
|
||||
|
||||
const StyledButton = styled('button')(({ theme }) => ({
|
||||
all: 'unset',
|
||||
lineHeight: 0.1,
|
||||
paddingTop: '1px',
|
||||
display: 'block',
|
||||
cursor: 'pointer',
|
||||
'& svg': {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
opacity: 0.5,
|
||||
},
|
||||
'&:hover svg, &:focus-visible svg': {
|
||||
opacity: 0.75,
|
||||
},
|
||||
}));
|
||||
|
||||
export const ContextFormChip = ({
|
||||
label,
|
||||
description,
|
||||
onRemove,
|
||||
}: IContextFormChipProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return (
|
||||
<li className={styles.container}>
|
||||
<StyledContainer>
|
||||
<div>
|
||||
<div className={styles.label}>{label}</div>
|
||||
<StyledLabel>{label}</StyledLabel>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(description)}
|
||||
show={() => (
|
||||
<div className={styles.description}>{description}</div>
|
||||
<StyledDescription>{description}</StyledDescription>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<button onClick={onRemove} className={styles.button}>
|
||||
<StyledButton onClick={onRemove}>
|
||||
<Cancel titleAccess="Remove" />
|
||||
</button>
|
||||
</li>
|
||||
</StyledButton>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
listStyleType: 'none',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '0.5rem',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
marginBottom: '1rem !important',
|
||||
},
|
||||
}));
|
@ -1,8 +1,14 @@
|
||||
import { useStyles } from 'component/context/ContectFormChip/ContextFormChipList.styles';
|
||||
import React from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
export const ContextFormChipList: React.FC = ({ children }) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const StyledContainer = styled('ul')(({ theme }) => ({
|
||||
listStyleType: 'none',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing(1),
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
marginBottom: '1rem !important',
|
||||
}));
|
||||
|
||||
return <ul className={styles.container}>{children}</ul>;
|
||||
};
|
||||
export const ContextFormChipList: React.FC = StyledContainer;
|
||||
|
@ -1,59 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
maxWidth: '470px',
|
||||
},
|
||||
form: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
input: { width: '100%', marginBottom: '1rem' },
|
||||
inputHeader: {
|
||||
marginBottom: '0.3rem',
|
||||
},
|
||||
label: {
|
||||
minWidth: '300px',
|
||||
[theme.breakpoints.down(600)]: {
|
||||
minWidth: 'auto',
|
||||
},
|
||||
},
|
||||
tagContainer: {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr auto',
|
||||
gap: '0.5rem',
|
||||
marginBottom: '1rem',
|
||||
},
|
||||
tagInput: {
|
||||
gridColumn: 1,
|
||||
},
|
||||
tagButton: {
|
||||
gridColumn: 2,
|
||||
},
|
||||
buttonContainer: {
|
||||
marginTop: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
cancelButton: {
|
||||
marginLeft: '1.5rem',
|
||||
},
|
||||
inputDescription: {
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
permissionErrorContainer: {
|
||||
position: 'relative',
|
||||
},
|
||||
errorMessage: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.error.main,
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
},
|
||||
switchContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: '-9px',
|
||||
},
|
||||
}));
|
@ -1,6 +1,12 @@
|
||||
import Input from 'component/common/Input/Input';
|
||||
import { TextField, Button, Switch, Typography } from '@mui/material';
|
||||
import { useStyles } from './ContextForm.styles';
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
Switch,
|
||||
Typography,
|
||||
styled,
|
||||
Theme,
|
||||
} from '@mui/material';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { ILegalValue } from 'interfaces/context';
|
||||
@ -27,6 +33,52 @@ interface IContextForm {
|
||||
|
||||
const ENTER = 'Enter';
|
||||
|
||||
const StyledForm = styled('form')({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
const StyledContainer = styled('div')({
|
||||
maxWidth: '470px',
|
||||
});
|
||||
|
||||
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||
marginBottom: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const styledInput = (theme: Theme) => ({
|
||||
width: '100%',
|
||||
marginBottom: theme.spacing(2),
|
||||
});
|
||||
|
||||
const StyledTagContainer = styled('div')(({ theme }) => ({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr auto',
|
||||
gap: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledInputHeader = styled('p')(({ theme }) => ({
|
||||
marginBottom: theme.spacing(0.5),
|
||||
}));
|
||||
|
||||
const StyledSwitchContainer = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: '-9px',
|
||||
});
|
||||
|
||||
const StyledButtonContainer = styled('div')({
|
||||
marginTop: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
});
|
||||
|
||||
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||
marginLeft: theme.spacing(3),
|
||||
}));
|
||||
|
||||
export const ContextForm: React.FC<IContextForm> = ({
|
||||
children,
|
||||
handleSubmit,
|
||||
@ -45,7 +97,6 @@ export const ContextForm: React.FC<IContextForm> = ({
|
||||
setErrors,
|
||||
clearErrors,
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const [value, setValue] = useState('');
|
||||
const [valueDesc, setValueDesc] = useState('');
|
||||
const [valueFocused, setValueFocused] = useState(false);
|
||||
@ -104,13 +155,13 @@ export const ContextForm: React.FC<IContextForm> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit} className={styles.form}>
|
||||
<div className={styles.container}>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledForm onSubmit={onSubmit}>
|
||||
<StyledContainer>
|
||||
<StyledInputDescription>
|
||||
What is your context name?
|
||||
</p>
|
||||
</StyledInputDescription>
|
||||
<Input
|
||||
className={styles.input}
|
||||
sx={styledInput}
|
||||
label="Context name"
|
||||
value={contextName}
|
||||
disabled={mode === 'Edit'}
|
||||
@ -121,11 +172,11 @@ export const ContextForm: React.FC<IContextForm> = ({
|
||||
onBlur={validateContext}
|
||||
autoFocus
|
||||
/>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledInputDescription>
|
||||
What is this context for?
|
||||
</p>
|
||||
</StyledInputDescription>
|
||||
<TextField
|
||||
className={styles.input}
|
||||
sx={styledInput}
|
||||
label="Context description (optional)"
|
||||
variant="outlined"
|
||||
multiline
|
||||
@ -134,14 +185,14 @@ export const ContextForm: React.FC<IContextForm> = ({
|
||||
size="small"
|
||||
onChange={e => setContextDesc(e.target.value)}
|
||||
/>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledInputDescription>
|
||||
Which values do you want to allow?
|
||||
</p>
|
||||
<div className={styles.tagContainer}>
|
||||
</StyledInputDescription>
|
||||
<StyledTagContainer>
|
||||
<TextField
|
||||
label="Legal value (optional)"
|
||||
name="value"
|
||||
className={styles.tagInput}
|
||||
sx={{ gridColumn: 1 }}
|
||||
value={value}
|
||||
error={Boolean(errors.tag)}
|
||||
helperText={errors.tag}
|
||||
@ -155,7 +206,7 @@ export const ContextForm: React.FC<IContextForm> = ({
|
||||
/>
|
||||
<TextField
|
||||
label="Value description (optional)"
|
||||
className={styles.tagInput}
|
||||
sx={{ gridColumn: 1 }}
|
||||
value={valueDesc}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
@ -166,7 +217,7 @@ export const ContextForm: React.FC<IContextForm> = ({
|
||||
inputProps={{ maxLength: 100 }}
|
||||
/>
|
||||
<Button
|
||||
className={styles.tagButton}
|
||||
sx={{ gridColumn: 2 }}
|
||||
startIcon={<Add />}
|
||||
onClick={addLegalValue}
|
||||
variant="outlined"
|
||||
@ -175,7 +226,7 @@ export const ContextForm: React.FC<IContextForm> = ({
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</StyledTagContainer>
|
||||
<ContextFormChipList>
|
||||
{legalValues.map(legalValue => {
|
||||
return (
|
||||
@ -188,7 +239,7 @@ export const ContextForm: React.FC<IContextForm> = ({
|
||||
);
|
||||
})}
|
||||
</ContextFormChipList>
|
||||
<p className={styles.inputHeader}>Custom stickiness</p>
|
||||
<StyledInputHeader>Custom stickiness</StyledInputHeader>
|
||||
<p>
|
||||
By enabling stickiness on this context field you can use it
|
||||
together with the flexible-rollout strategy. This will
|
||||
@ -203,21 +254,21 @@ export const ContextForm: React.FC<IContextForm> = ({
|
||||
Read more
|
||||
</a>
|
||||
</p>
|
||||
<div className={styles.switchContainer}>
|
||||
<StyledSwitchContainer>
|
||||
<Switch
|
||||
checked={stickiness}
|
||||
value={stickiness}
|
||||
onChange={() => setStickiness(!stickiness)}
|
||||
/>
|
||||
<Typography>{stickiness ? 'On' : 'Off'}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
</StyledSwitchContainer>
|
||||
</StyledContainer>
|
||||
<StyledButtonContainer>
|
||||
{children}
|
||||
<Button onClick={onCancel} className={styles.cancelButton}>
|
||||
<StyledCancelButton onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</StyledCancelButton>
|
||||
</StyledButtonContainer>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: `1px solid ${theme.palette.grey[200]}`,
|
||||
padding: '1.5rem',
|
||||
borderRadius: '5px',
|
||||
margin: '1.5rem 0',
|
||||
minWidth: '450px',
|
||||
},
|
||||
icon: {
|
||||
fill: theme.palette.inactiveIcon,
|
||||
marginRight: '0.5rem',
|
||||
},
|
||||
header: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: '0.25rem',
|
||||
},
|
||||
infoContainer: {
|
||||
marginTop: '1rem',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
infoInnerContainer: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
infoTitle: { fontWeight: 'bold', marginBottom: '0.25rem' },
|
||||
}));
|
@ -1,38 +0,0 @@
|
||||
import { CloudCircle } from '@mui/icons-material';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
import { useStyles } from 'component/environments/EnvironmentCard/EnvironmentCard.styles';
|
||||
|
||||
interface IEnvironmentProps {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const EnvironmentCard = ({ name, type }: IEnvironmentProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<CloudCircle className={styles.icon} /> Environment
|
||||
</div>
|
||||
|
||||
<div className={styles.infoContainer}>
|
||||
<div className={styles.infoInnerContainer}>
|
||||
<div className={styles.infoTitle}>Id</div>
|
||||
<div>
|
||||
<StringTruncator
|
||||
text={name}
|
||||
maxWidth={'250'}
|
||||
maxLength={30}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.infoInnerContainer}>
|
||||
<div className={styles.infoTitle}>Type</div>
|
||||
<div>{type}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentCard;
|
@ -1,46 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
maxWidth: '440px',
|
||||
},
|
||||
form: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
input: { width: '100%', marginBottom: '1rem' },
|
||||
label: {
|
||||
minWidth: '30px',
|
||||
[theme.breakpoints.down(600)]: {
|
||||
minWidth: 'auto',
|
||||
},
|
||||
},
|
||||
buttonContainer: {
|
||||
marginTop: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
cancelButton: {
|
||||
marginLeft: '1.5rem',
|
||||
},
|
||||
inputDescription: {
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
formHeader: {
|
||||
fontWeight: 'normal',
|
||||
marginTop: '0',
|
||||
},
|
||||
header: {
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
permissionErrorContainer: {
|
||||
position: 'relative',
|
||||
},
|
||||
errorMessage: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.error.main,
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
},
|
||||
}));
|
@ -1,5 +1,4 @@
|
||||
import { Button } from '@mui/material';
|
||||
import { useStyles } from './EnvironmentForm.styles';
|
||||
import { Button, styled } from '@mui/material';
|
||||
import React from 'react';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import EnvironmentTypeSelector from './EnvironmentTypeSelector/EnvironmentTypeSelector';
|
||||
@ -18,6 +17,40 @@ interface IEnvironmentForm {
|
||||
clearErrors: () => void;
|
||||
}
|
||||
|
||||
const StyledForm = styled('form')({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
const StyledFormHeader = styled('h3')({
|
||||
fontWeight: 'normal',
|
||||
marginTop: '0',
|
||||
});
|
||||
|
||||
const StyledContainer = styled('div')({
|
||||
maxWidth: '440px',
|
||||
});
|
||||
|
||||
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||
marginBottom: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledInput = styled(Input)(({ theme }) => ({
|
||||
width: '100%',
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||
marginTop: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
}));
|
||||
|
||||
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||
marginLeft: theme.spacing(3),
|
||||
}));
|
||||
|
||||
const EnvironmentForm: React.FC<IEnvironmentForm> = ({
|
||||
children,
|
||||
handleSubmit,
|
||||
@ -31,18 +64,15 @@ const EnvironmentForm: React.FC<IEnvironmentForm> = ({
|
||||
mode,
|
||||
clearErrors,
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={styles.form}>
|
||||
<h3 className={styles.formHeader}>Environment information</h3>
|
||||
<StyledForm onSubmit={handleSubmit}>
|
||||
<StyledFormHeader>Environment information</StyledFormHeader>
|
||||
|
||||
<div className={styles.container}>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledContainer>
|
||||
<StyledInputDescription>
|
||||
What is your environment name? (Can't be changed later)
|
||||
</p>
|
||||
<Input
|
||||
className={styles.input}
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
label="Environment name"
|
||||
value={name}
|
||||
onChange={e => setName(trim(e.target.value))}
|
||||
@ -54,21 +84,21 @@ const EnvironmentForm: React.FC<IEnvironmentForm> = ({
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledInputDescription>
|
||||
What type of environment do you want to create?
|
||||
</p>
|
||||
</StyledInputDescription>
|
||||
<EnvironmentTypeSelector
|
||||
onChange={e => setType(e.currentTarget.value)}
|
||||
value={type}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
</StyledContainer>
|
||||
<StyledButtonContainer>
|
||||
{children}
|
||||
<Button onClick={handleCancel} className={styles.cancelButton}>
|
||||
<StyledCancelButton onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</StyledCancelButton>
|
||||
</StyledButtonContainer>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
radioGroup: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
formHeader: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
marginTop: '1.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
radioBtnGroup: { display: 'flex', flexDirection: 'column' },
|
||||
}));
|
@ -3,8 +3,8 @@ import {
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import { useStyles } from './EnvironmentTypeSelector.styles';
|
||||
import React from 'react';
|
||||
|
||||
interface IEnvironmentTypeSelectorProps {
|
||||
@ -12,20 +12,23 @@ interface IEnvironmentTypeSelectorProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const StyledRadioGroup = styled(RadioGroup)({
|
||||
flexDirection: 'row',
|
||||
});
|
||||
|
||||
const StyledRadioButtonGroup = styled('div')({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
const EnvironmentTypeSelector = ({
|
||||
onChange,
|
||||
value,
|
||||
}: IEnvironmentTypeSelectorProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
<RadioGroup
|
||||
data-loading
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={styles.radioGroup}
|
||||
>
|
||||
<div className={styles.radioBtnGroup}>
|
||||
<StyledRadioGroup data-loading value={value} onChange={onChange}>
|
||||
<StyledRadioButtonGroup>
|
||||
<FormControlLabel
|
||||
value="development"
|
||||
label="Development"
|
||||
@ -36,8 +39,8 @@ const EnvironmentTypeSelector = ({
|
||||
label="Test"
|
||||
control={<Radio />}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
</StyledRadioButtonGroup>
|
||||
<StyledRadioButtonGroup>
|
||||
<FormControlLabel
|
||||
value="preproduction"
|
||||
label="Pre production"
|
||||
@ -48,8 +51,8 @@ const EnvironmentTypeSelector = ({
|
||||
label="Production"
|
||||
control={<Radio />}
|
||||
/>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</StyledRadioButtonGroup>
|
||||
</StyledRadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
@ -1,68 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
maxWidth: '400px',
|
||||
},
|
||||
form: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
input: { width: '100%', marginBottom: '1rem' },
|
||||
selectInput: {
|
||||
marginBottom: '1rem',
|
||||
minWidth: '400px',
|
||||
[theme.breakpoints.down(600)]: {
|
||||
minWidth: '379px',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
minWidth: '300px',
|
||||
[theme.breakpoints.down(600)]: {
|
||||
minWidth: 'auto',
|
||||
},
|
||||
},
|
||||
buttonContainer: {
|
||||
marginTop: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
cancelButton: {
|
||||
marginLeft: '1.5rem',
|
||||
},
|
||||
inputDescription: {
|
||||
marginBottom: '0.5rem',
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
typeDescription: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.text.secondary,
|
||||
top: '-13px',
|
||||
position: 'relative',
|
||||
},
|
||||
formHeader: {
|
||||
fontWeight: 'normal',
|
||||
marginTop: '0',
|
||||
},
|
||||
header: {
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
permissionErrorContainer: {
|
||||
position: 'relative',
|
||||
},
|
||||
errorMessage: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.error.main,
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
},
|
||||
roleSubtitle: {
|
||||
margin: '0.5rem 0',
|
||||
},
|
||||
flexRow: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '0.5rem',
|
||||
},
|
||||
}));
|
@ -2,10 +2,11 @@ import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
styled,
|
||||
Switch,
|
||||
Theme,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useStyles } from './FeatureForm.styles';
|
||||
import FeatureTypeSelect from '../FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect';
|
||||
import { CF_DESC_ID, CF_NAME_ID, CF_TYPE_ID } from 'utils/testIds';
|
||||
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
|
||||
@ -39,6 +40,66 @@ interface IFeatureToggleForm {
|
||||
clearErrors: () => void;
|
||||
}
|
||||
|
||||
const StyledForm = styled('form')({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
const StyledContainer = styled('div')({
|
||||
maxWidth: '400px',
|
||||
});
|
||||
|
||||
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||
marginBottom: theme.spacing(1),
|
||||
color: theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
const StyledInput = styled(Input)(({ theme }) => ({
|
||||
width: '100%',
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledFormControl = styled(FormControl)(({ theme }) => ({
|
||||
width: '100%',
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const styledSelectInput = (theme: Theme) => ({
|
||||
marginBottom: theme.spacing(2),
|
||||
minWidth: '400px',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
minWidth: '379px',
|
||||
},
|
||||
});
|
||||
|
||||
const StyledTypeDescription = styled('p')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.text.secondary,
|
||||
top: '-13px',
|
||||
position: 'relative',
|
||||
}));
|
||||
|
||||
const StyledButtonContainer = styled('div')({
|
||||
marginTop: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
});
|
||||
|
||||
const StyledRow = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||
marginLeft: theme.spacing(3),
|
||||
}));
|
||||
|
||||
const styledTypography = (theme: Theme) => ({
|
||||
margin: theme.spacing(1, 0),
|
||||
});
|
||||
|
||||
const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
children,
|
||||
type,
|
||||
@ -58,7 +119,6 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
mode,
|
||||
clearErrors,
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const { featureTypes } = useFeatureTypes();
|
||||
const navigate = useNavigate();
|
||||
const { permissions } = useAuthPermissions();
|
||||
@ -69,15 +129,14 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={styles.form}>
|
||||
<div className={styles.container}>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledForm onSubmit={handleSubmit}>
|
||||
<StyledContainer>
|
||||
<StyledInputDescription>
|
||||
What would you like to call your toggle?
|
||||
</p>
|
||||
<Input
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
autoFocus
|
||||
disabled={mode === 'Edit'}
|
||||
className={styles.input}
|
||||
label="Name"
|
||||
id="feature-toggle-name"
|
||||
error={Boolean(errors.name)}
|
||||
@ -88,10 +147,11 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
data-testid={CF_NAME_ID}
|
||||
onBlur={validateToggleName}
|
||||
/>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledInputDescription>
|
||||
What kind of feature toggle do you want?
|
||||
</p>
|
||||
</StyledInputDescription>
|
||||
<FeatureTypeSelect
|
||||
sx={styledSelectInput}
|
||||
value={type}
|
||||
onChange={setType}
|
||||
label={'Toggle type'}
|
||||
@ -99,17 +159,16 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
editable
|
||||
data-testid={CF_TYPE_ID}
|
||||
IconComponent={KeyboardArrowDownOutlined}
|
||||
className={styles.selectInput}
|
||||
/>
|
||||
<p className={styles.typeDescription}>
|
||||
<StyledTypeDescription>
|
||||
{renderToggleDescription()}
|
||||
</p>
|
||||
</StyledTypeDescription>
|
||||
<ConditionallyRender
|
||||
condition={editable}
|
||||
show={
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledInputDescription>
|
||||
In which project do you want to save the toggle?
|
||||
</p>
|
||||
</StyledInputDescription>
|
||||
}
|
||||
/>
|
||||
<FeatureProjectSelect
|
||||
@ -123,14 +182,13 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
enabled={editable}
|
||||
filter={projectFilterGenerator(permissions, CREATE_FEATURE)}
|
||||
IconComponent={KeyboardArrowDownOutlined}
|
||||
className={styles.selectInput}
|
||||
sx={styledSelectInput}
|
||||
/>
|
||||
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledInputDescription>
|
||||
How would you describe your feature toggle?
|
||||
</p>
|
||||
<Input
|
||||
className={styles.input}
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
multiline
|
||||
rows={4}
|
||||
label="Description"
|
||||
@ -139,10 +197,10 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
data-testid={CF_DESC_ID}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
<FormControl className={styles.input}>
|
||||
<StyledFormControl>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
className={styles.roleSubtitle}
|
||||
sx={styledTypography}
|
||||
data-loading
|
||||
component="h2"
|
||||
>
|
||||
@ -160,7 +218,7 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
the impression data documentation
|
||||
</a>
|
||||
</p>
|
||||
<div className={styles.flexRow}>
|
||||
<StyledRow>
|
||||
<FormControlLabel
|
||||
labelPlacement="start"
|
||||
style={{ marginLeft: 0 }}
|
||||
@ -175,17 +233,17 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
}
|
||||
label="Enable impression data"
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
</div>
|
||||
</StyledRow>
|
||||
</StyledFormControl>
|
||||
</StyledContainer>
|
||||
|
||||
<div className={styles.buttonContainer}>
|
||||
<StyledButtonContainer>
|
||||
{children}
|
||||
<Button onClick={handleCancel} className={styles.cancelButton}>
|
||||
<StyledCancelButton onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</StyledCancelButton>
|
||||
</StyledButtonContainer>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingTop: theme.spacing(2),
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
textAlign: 'center',
|
||||
color: theme.palette.text.primary,
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
description: {
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing(3),
|
||||
},
|
||||
}));
|
@ -1,10 +1,9 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Box } from '@mui/material';
|
||||
import { Box, Button, styled } from '@mui/material';
|
||||
import { SectionSeparator } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/SectionSeparator/SectionSeparator';
|
||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { useStyles } from './FeatureStrategyEmpty.styles';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
@ -23,12 +22,33 @@ interface IFeatureStrategyEmptyProps {
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingTop: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledTitle = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
textAlign: 'center',
|
||||
color: theme.palette.text.primary,
|
||||
marginBottom: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledDescription = styled('p')(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing(3),
|
||||
}));
|
||||
|
||||
export const FeatureStrategyEmpty = ({
|
||||
projectId,
|
||||
featureId,
|
||||
environmentId,
|
||||
}: IFeatureStrategyEmptyProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const { addStrategyToFeature } = useFeatureStrategyApi();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
@ -122,16 +142,16 @@ export const FeatureStrategyEmpty = ({
|
||||
}
|
||||
/>
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>
|
||||
<StyledContainer>
|
||||
<StyledTitle>
|
||||
You have not defined any strategies yet.
|
||||
</div>
|
||||
<p className={styles.description}>
|
||||
</StyledTitle>
|
||||
<StyledDescription>
|
||||
Strategies added in this environment will only be executed
|
||||
if the SDK is using an{' '}
|
||||
<Link to="/admin/api">API key configured</Link> for this
|
||||
environment.
|
||||
</p>
|
||||
</StyledDescription>
|
||||
<Box
|
||||
sx={{
|
||||
w: '100%',
|
||||
@ -211,7 +231,7 @@ export const FeatureStrategyEmpty = ({
|
||||
Roll out to a percentage of your userbase.
|
||||
</AddFromTemplateCard>
|
||||
</Box>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,37 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
form: {
|
||||
display: 'grid',
|
||||
gap: '1rem',
|
||||
},
|
||||
hr: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
margin: '1rem 0',
|
||||
border: 'none',
|
||||
background: theme.palette.grey[200],
|
||||
},
|
||||
title: {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto 1fr',
|
||||
gridGap: '.5rem',
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
},
|
||||
icon: {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
name: {
|
||||
display: 'block',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
fontWeight: theme.fontWeight.thin,
|
||||
},
|
||||
buttons: {
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
gap: '1rem',
|
||||
paddingBottom: '5rem',
|
||||
},
|
||||
}));
|
@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Alert, Button } from '@mui/material';
|
||||
import { Alert, Button, styled } from '@mui/material';
|
||||
import {
|
||||
IFeatureStrategy,
|
||||
IFeatureStrategyParameters,
|
||||
@ -10,7 +10,6 @@ import { FeatureStrategyType } from '../FeatureStrategyType/FeatureStrategyType'
|
||||
import { FeatureStrategyEnabled } from './FeatureStrategyEnabled/FeatureStrategyEnabled';
|
||||
import { FeatureStrategyConstraints } from '../FeatureStrategyConstraints/FeatureStrategyConstraints';
|
||||
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||
import { useStyles } from './FeatureStrategyForm.styles';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { STRATEGY_FORM_SUBMIT_ID } from 'utils/testIds';
|
||||
@ -48,6 +47,26 @@ interface IFeatureStrategyFormProps {
|
||||
errors: IFormErrors;
|
||||
}
|
||||
|
||||
const StyledForm = styled('form')(({ theme }) => ({
|
||||
display: 'grid',
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledHr = styled('hr')(({ theme }) => ({
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
margin: theme.spacing(2, 0),
|
||||
border: 'none',
|
||||
background: theme.palette.tertiary.light,
|
||||
}));
|
||||
|
||||
const StyledButtons = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
gap: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(10),
|
||||
}));
|
||||
|
||||
export const FeatureStrategyForm = ({
|
||||
projectId,
|
||||
feature,
|
||||
@ -62,7 +81,6 @@ export const FeatureStrategyForm = ({
|
||||
errors,
|
||||
isChangeRequest,
|
||||
}: IFeatureStrategyFormProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const [showProdGuard, setShowProdGuard] = useState(false);
|
||||
const hasValidConstraints = useConstraintsValidation(strategy.constraints);
|
||||
const enableProdGuard = useFeatureStrategyProdGuard(feature, environmentId);
|
||||
@ -148,7 +166,7 @@ export const FeatureStrategyForm = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<form className={styles.form} onSubmit={onSubmitWithValidation}>
|
||||
<StyledForm onSubmit={onSubmitWithValidation}>
|
||||
<ConditionallyRender
|
||||
condition={hasChangeRequestInReviewForEnvironment}
|
||||
show={alert}
|
||||
@ -188,7 +206,7 @@ export const FeatureStrategyForm = ({
|
||||
}
|
||||
/>
|
||||
</FeatureStrategyEnabled>
|
||||
<hr className={styles.hr} />
|
||||
<StyledHr />
|
||||
<ConditionallyRender
|
||||
condition={Boolean(uiConfig.flags.SE)}
|
||||
show={
|
||||
@ -204,7 +222,7 @@ export const FeatureStrategyForm = ({
|
||||
strategy={strategy}
|
||||
setStrategy={setStrategy}
|
||||
/>
|
||||
<hr className={styles.hr} />
|
||||
<StyledHr />
|
||||
<FeatureStrategyType
|
||||
strategy={strategy}
|
||||
strategyDefinition={strategyDefinition}
|
||||
@ -213,8 +231,8 @@ export const FeatureStrategyForm = ({
|
||||
errors={errors}
|
||||
hasAccess={access}
|
||||
/>
|
||||
<hr className={styles.hr} />
|
||||
<div className={styles.buttons}>
|
||||
<StyledHr />
|
||||
<StyledButtons>
|
||||
<PermissionButton
|
||||
permission={permission}
|
||||
projectId={feature.project}
|
||||
@ -248,7 +266,7 @@ export const FeatureStrategyForm = ({
|
||||
loading={loading}
|
||||
label="Save strategy"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</StyledButtons>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
||||
|
@ -1,40 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
card: {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '3rem 1fr',
|
||||
width: '20rem',
|
||||
padding: '1rem',
|
||||
color: 'inherit',
|
||||
textDecoration: 'inherit',
|
||||
lineHeight: 1.25,
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
borderColor: theme.palette.grey[400],
|
||||
borderRadius: theme.spacing(1),
|
||||
'&:hover, &:focus': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
width: '2rem',
|
||||
height: 'auto',
|
||||
'& > svg': {
|
||||
// Styling for SVG icons.
|
||||
fill: theme.palette.primary.main,
|
||||
},
|
||||
'& > div': {
|
||||
// Styling for the Rollout icon.
|
||||
height: '1rem',
|
||||
marginLeft: '-.75rem',
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
},
|
||||
description: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
},
|
||||
}));
|
@ -1,12 +1,12 @@
|
||||
import { IStrategy } from 'interfaces/strategy';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useStyles } from './FeatureStrategyMenuCard.styles';
|
||||
import {
|
||||
getFeatureStrategyIcon,
|
||||
formatStrategyName,
|
||||
} from 'utils/strategyNames';
|
||||
import { formatCreateStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
interface IFeatureStrategyMenuCardProps {
|
||||
projectId: string;
|
||||
@ -15,13 +15,52 @@ interface IFeatureStrategyMenuCardProps {
|
||||
strategy: IStrategy;
|
||||
}
|
||||
|
||||
const StyledIcon = styled('div')(({ theme }) => ({
|
||||
width: theme.spacing(4),
|
||||
height: 'auto',
|
||||
'& > svg': {
|
||||
// Styling for SVG icons.
|
||||
fill: theme.palette.primary.main,
|
||||
},
|
||||
'& > div': {
|
||||
// Styling for the Rollout icon.
|
||||
height: theme.spacing(2),
|
||||
marginLeft: '-.75rem',
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledDescription = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
const StyledName = styled(StringTruncator)(({ theme }) => ({
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
}));
|
||||
|
||||
const StyledCard = styled(Link)(({ theme }) => ({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '3rem 1fr',
|
||||
width: '20rem',
|
||||
padding: theme.spacing(2),
|
||||
color: 'inherit',
|
||||
textDecoration: 'inherit',
|
||||
lineHeight: 1.25,
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'solid',
|
||||
borderColor: theme.palette.dividerAlternative,
|
||||
borderRadius: theme.spacing(1),
|
||||
'&:hover, &:focus': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}));
|
||||
|
||||
export const FeatureStrategyMenuCard = ({
|
||||
projectId,
|
||||
featureId,
|
||||
environmentId,
|
||||
strategy,
|
||||
}: IFeatureStrategyMenuCardProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const StrategyIcon = getFeatureStrategyIcon(strategy.name);
|
||||
const strategyName = formatStrategyName(strategy.name);
|
||||
|
||||
@ -33,19 +72,18 @@ export const FeatureStrategyMenuCard = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<Link to={createStrategyPath} className={styles.card}>
|
||||
<div className={styles.icon}>
|
||||
<StyledCard to={createStrategyPath}>
|
||||
<StyledIcon>
|
||||
<StrategyIcon />
|
||||
</div>
|
||||
</StyledIcon>
|
||||
<div>
|
||||
<StringTruncator
|
||||
<StyledName
|
||||
text={strategy.displayName || strategyName}
|
||||
className={styles.name}
|
||||
maxWidth="200"
|
||||
maxLength={25}
|
||||
/>
|
||||
<div className={styles.description}>{strategy.description}</div>
|
||||
<StyledDescription>{strategy.description}</StyledDescription>
|
||||
</div>
|
||||
</Link>
|
||||
</StyledCard>
|
||||
);
|
||||
};
|
||||
|
@ -1,9 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
divider: {
|
||||
border: `1px dashed ${theme.palette.divider}`,
|
||||
marginTop: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
}));
|
@ -6,22 +6,24 @@ import {
|
||||
IAutocompleteBoxOption,
|
||||
} from 'component/common/AutocompleteBox/AutocompleteBox';
|
||||
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
|
||||
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
|
||||
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs';
|
||||
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
||||
import { Divider, Typography } from '@mui/material';
|
||||
import { Divider, styled, Typography } from '@mui/material';
|
||||
|
||||
interface IFeatureStrategySegmentProps {
|
||||
segments: ISegment[];
|
||||
setSegments: React.Dispatch<React.SetStateAction<ISegment[]>>;
|
||||
}
|
||||
|
||||
const StyledDivider = styled(Divider)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
export const FeatureStrategySegment = ({
|
||||
segments: selectedSegments,
|
||||
setSegments: setSelectedSegments,
|
||||
}: IFeatureStrategySegmentProps) => {
|
||||
const { segments: allSegments } = useSegments();
|
||||
const { classes: styles } = useStyles();
|
||||
const { strategySegmentsLimit } = useSegmentLimits();
|
||||
|
||||
const atStrategySegmentsLimit: boolean = Boolean(
|
||||
@ -68,7 +70,7 @@ export const FeatureStrategySegment = ({
|
||||
segments={selectedSegments}
|
||||
setSegments={setSelectedSegments}
|
||||
/>
|
||||
<Divider className={styles.divider} />
|
||||
<StyledDivider />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,29 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
chip: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.25rem',
|
||||
paddingInlineStart: '1rem',
|
||||
paddingInlineEnd: '0.5rem',
|
||||
paddingBlockStart: 4,
|
||||
paddingBlockEnd: 4,
|
||||
borderRadius: '100rem',
|
||||
background: theme.palette.featureStrategySegmentChipBackground,
|
||||
color: 'white',
|
||||
},
|
||||
link: {
|
||||
marginRight: '.5rem',
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
},
|
||||
button: {
|
||||
all: 'unset',
|
||||
height: '1rem',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
icon: {
|
||||
fontSize: '1rem',
|
||||
},
|
||||
}));
|
@ -2,10 +2,9 @@ import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ISegment } from 'interfaces/segment';
|
||||
import { Clear, VisibilityOff, Visibility } from '@mui/icons-material';
|
||||
import { useStyles } from './FeatureStrategySegmentChip.styles';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { constraintAccordionListId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { styled, Theme, Tooltip } from '@mui/material';
|
||||
|
||||
interface IFeatureStrategySegmentListProps {
|
||||
segment: ISegment;
|
||||
@ -14,14 +13,39 @@ interface IFeatureStrategySegmentListProps {
|
||||
setPreview: React.Dispatch<React.SetStateAction<ISegment | undefined>>;
|
||||
}
|
||||
|
||||
const StyledChip = styled('span')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(0.5),
|
||||
paddingInlineStart: theme.spacing(2),
|
||||
paddingInlineEnd: theme.spacing(1),
|
||||
paddingBlockStart: theme.spacing(0.5),
|
||||
paddingBlockEnd: theme.spacing(0.5),
|
||||
borderRadius: '100rem',
|
||||
background: theme.palette.featureStrategySegmentChipBackground,
|
||||
color: theme.palette.text.tertiaryContrast,
|
||||
}));
|
||||
|
||||
const StyledButton = styled('button')(({ theme }) => ({
|
||||
all: 'unset',
|
||||
height: theme.spacing(2),
|
||||
cursor: 'pointer',
|
||||
}));
|
||||
|
||||
const StyledLink = styled(Link)(({ theme }) => ({
|
||||
marginRight: theme.spacing(1),
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
}));
|
||||
|
||||
const styledIcon = (theme: Theme) => ({ fontSize: theme.fontSizes.bodySize });
|
||||
|
||||
export const FeatureStrategySegmentChip = ({
|
||||
segment,
|
||||
setSegments,
|
||||
preview,
|
||||
setPreview,
|
||||
}: IFeatureStrategySegmentListProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
const onRemove = () => {
|
||||
setSegments(prev => {
|
||||
return prev.filter(s => s.id !== segment.id);
|
||||
@ -40,8 +64,8 @@ export const FeatureStrategySegmentChip = ({
|
||||
const togglePreviewIcon = (
|
||||
<ConditionallyRender
|
||||
condition={segment === preview}
|
||||
show={<VisibilityOff titleAccess="Hide" className={styles.icon} />}
|
||||
elseShow={<Visibility titleAccess="Show" className={styles.icon} />}
|
||||
show={<VisibilityOff titleAccess="Hide" sx={styledIcon} />}
|
||||
elseShow={<Visibility titleAccess="Show" sx={styledIcon} />}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -51,34 +75,25 @@ export const FeatureStrategySegmentChip = ({
|
||||
: 'Preview segment constraints';
|
||||
|
||||
return (
|
||||
<span className={styles.chip}>
|
||||
<Link
|
||||
to={`/segments/edit/${segment.id}`}
|
||||
target="_blank"
|
||||
className={styles.link}
|
||||
>
|
||||
<StyledChip>
|
||||
<StyledLink to={`/segments/edit/${segment.id}`} target="_blank">
|
||||
{segment.name}
|
||||
</Link>
|
||||
</StyledLink>
|
||||
<Tooltip title={previewIconTooltip} arrow>
|
||||
<button
|
||||
<StyledButton
|
||||
type="button"
|
||||
onClick={onTogglePreview}
|
||||
className={styles.button}
|
||||
aria-expanded={segment === preview}
|
||||
aria-controls={constraintAccordionListId}
|
||||
>
|
||||
{togglePreviewIcon}
|
||||
</button>
|
||||
</StyledButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Remove segment" arrow>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRemove}
|
||||
className={styles.button}
|
||||
>
|
||||
<Clear titleAccess="Remove" className={styles.icon} />
|
||||
</button>
|
||||
<StyledButton type="button" onClick={onRemove}>
|
||||
<Clear titleAccess="Remove" sx={styledIcon} />
|
||||
</StyledButton>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</StyledChip>
|
||||
);
|
||||
};
|
||||
|
@ -1,29 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
title: {
|
||||
margin: 0,
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.fontWeight.thin,
|
||||
},
|
||||
list: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '0.5rem',
|
||||
},
|
||||
and: {
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
padding: theme.spacing(0.75, 1),
|
||||
display: 'block',
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
alignItems: 'center',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
lineHeight: 1,
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondaryContainer,
|
||||
},
|
||||
selectedSegmentsLabel: {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
}));
|
@ -1,20 +1,42 @@
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import { ISegment } from 'interfaces/segment';
|
||||
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.styles';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { FeatureStrategySegmentChip } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip';
|
||||
import { SegmentItem } from 'component/common/SegmentItem/SegmentItem';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
interface IFeatureStrategySegmentListProps {
|
||||
segments: ISegment[];
|
||||
setSegments: React.Dispatch<React.SetStateAction<ISegment[]>>;
|
||||
}
|
||||
|
||||
const StyledList = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledSelectedSegmentsLabel = styled('p')(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
const StyledAnd = styled('p')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
padding: theme.spacing(0.75, 1),
|
||||
display: 'block',
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
alignItems: 'center',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
lineHeight: 1,
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondaryContainer,
|
||||
}));
|
||||
|
||||
export const FeatureStrategySegmentList = ({
|
||||
segments,
|
||||
setSegments,
|
||||
}: IFeatureStrategySegmentListProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const [preview, setPreview] = useState<ISegment>();
|
||||
const lastSegmentIndex = segments.length - 1;
|
||||
|
||||
@ -27,12 +49,12 @@ export const FeatureStrategySegmentList = ({
|
||||
<ConditionallyRender
|
||||
condition={segments && segments.length > 0}
|
||||
show={
|
||||
<p className={styles.selectedSegmentsLabel}>
|
||||
<StyledSelectedSegmentsLabel>
|
||||
Selected Segments
|
||||
</p>
|
||||
</StyledSelectedSegmentsLabel>
|
||||
}
|
||||
/>
|
||||
<div className={styles.list}>
|
||||
<StyledList>
|
||||
{segments.map((segment, i) => (
|
||||
<Fragment key={segment.id}>
|
||||
<FeatureStrategySegmentChip
|
||||
@ -43,11 +65,11 @@ export const FeatureStrategySegmentList = ({
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={i < lastSegmentIndex}
|
||||
show={<span className={styles.and}>AND</span>}
|
||||
show={<StyledAnd>AND</StyledAnd>}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</StyledList>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(preview)}
|
||||
show={() => <SegmentItem segment={preview!} isExpanded />}
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
status: {
|
||||
color: theme.palette.success.dark,
|
||||
fontSize: 'inherit',
|
||||
},
|
||||
stale: {
|
||||
color: theme.palette.error.dark,
|
||||
},
|
||||
}));
|
@ -1,23 +1,41 @@
|
||||
import { VFC } from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { useStyles } from './FeatureStaleCell.styles';
|
||||
import classnames from 'classnames';
|
||||
import { Box, styled, Theme, Typography } from '@mui/material';
|
||||
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
interface IFeatureStaleCellProps {
|
||||
value?: boolean;
|
||||
}
|
||||
|
||||
const staleStatus = (theme: Theme) => ({
|
||||
color: theme.palette.error.dark,
|
||||
fontSize: 'inherit',
|
||||
});
|
||||
|
||||
const activeStatus = (theme: Theme) => ({
|
||||
color: theme.palette.success.dark,
|
||||
fontSize: 'inherit',
|
||||
});
|
||||
|
||||
const StyledBox = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(1.5, 2),
|
||||
}));
|
||||
|
||||
export const FeatureStaleCell: VFC<IFeatureStaleCellProps> = ({ value }) => {
|
||||
const { classes: styles } = useStyles();
|
||||
return (
|
||||
<Box sx={{ py: 1.5, px: 2 }}>
|
||||
<Typography
|
||||
component="span"
|
||||
className={classnames(styles.status, value && styles.stale)}
|
||||
data-loading
|
||||
>
|
||||
{value ? 'Stale' : 'Active'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<StyledBox>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(value)}
|
||||
show={
|
||||
<Typography component="span" sx={staleStatus} data-loading>
|
||||
Stale
|
||||
</Typography>
|
||||
}
|
||||
elseShow={
|
||||
<Typography component="span" sx={activeStatus} data-loading>
|
||||
Active
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
||||
|
@ -1,9 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
borderRadius: '12.5px',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
padding: '2rem',
|
||||
},
|
||||
}));
|
@ -1,12 +1,17 @@
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { useStyles } from './FeatureLog.styles';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { EventLog } from 'component/events/EventLog/EventLog';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
borderRadius: theme.spacing(1.5),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
padding: theme.spacing(4),
|
||||
}));
|
||||
|
||||
const FeatureLog = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { classes: styles } = useStyles();
|
||||
const { feature } = useFeature(projectId, featureId);
|
||||
|
||||
if (!feature.name) {
|
||||
@ -14,14 +19,14 @@ const FeatureLog = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<StyledContainer>
|
||||
<EventLog
|
||||
title="Event log"
|
||||
project={projectId}
|
||||
feature={featureId}
|
||||
displayInline
|
||||
/>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,9 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
mobileMarginTop: {
|
||||
[theme.breakpoints.down('md')]: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
}));
|
@ -6,23 +6,27 @@ import {
|
||||
FeatureMetricsHours,
|
||||
} from './FeatureMetricsHours/FeatureMetricsHours';
|
||||
import { IFeatureMetricsRaw } from 'interfaces/featureToggle';
|
||||
import { Grid } from '@mui/material';
|
||||
import { Grid, styled } from '@mui/material';
|
||||
import { FeatureMetricsContent } from './FeatureMetricsContent/FeatureMetricsContent';
|
||||
import { useQueryStringNumberState } from 'hooks/useQueryStringNumberState';
|
||||
import { useQueryStringState } from 'hooks/useQueryStringState';
|
||||
import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useStyles } from './FeatureMetrics.styles';
|
||||
import { usePageTitle } from 'hooks/usePageTitle';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
[theme.breakpoints.down('md')]: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
export const FeatureMetrics = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const environments = useFeatureMetricsEnvironments(projectId, featureId);
|
||||
const applications = useFeatureMetricsApplications(featureId);
|
||||
const { classes: styles } = useStyles();
|
||||
usePageTitle('Metrics');
|
||||
|
||||
const [hoursBack = FEATURE_METRIC_HOURS_BACK_MAX, setHoursBack] =
|
||||
@ -91,12 +95,12 @@ export const FeatureMetrics = () => {
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={2}>
|
||||
<div className={styles.mobileMarginTop}>
|
||||
<StyledContainer>
|
||||
<FeatureMetricsHours
|
||||
hoursBack={hoursBack}
|
||||
setHoursBack={setHoursBack}
|
||||
/>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<FeatureMetricsContent
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
title: {
|
||||
margin: 0,
|
||||
marginBottom: '.5rem',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
fontWeight: theme.fontWeight.thin,
|
||||
color: theme.palette.grey[800],
|
||||
},
|
||||
list: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '.5rem',
|
||||
listStyleType: 'none',
|
||||
padding: 0,
|
||||
minHeight: '100%',
|
||||
},
|
||||
item: {
|
||||
'& > [aria-pressed=true]': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
},
|
||||
}));
|
@ -1,7 +1,6 @@
|
||||
import { Chip } from '@mui/material';
|
||||
import { Chip, styled } from '@mui/material';
|
||||
import { useMemo } from 'react';
|
||||
import { useStyles } from './FeatureMetricsChips.styles';
|
||||
import { useThemeStyles } from 'themes/themeStyles';
|
||||
import { focusable } from 'themes/themeStyles';
|
||||
|
||||
interface IFeatureMetricsChipsProps {
|
||||
title: string;
|
||||
@ -10,15 +9,36 @@ interface IFeatureMetricsChipsProps {
|
||||
setValue: (value: string) => void;
|
||||
}
|
||||
|
||||
const StyledTitle = styled('h2')(({ theme }) => ({
|
||||
margin: 0,
|
||||
marginBottom: theme.spacing(1),
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
fontWeight: theme.fontWeight.thin,
|
||||
color: theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
const StyledList = styled('ul')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing(1),
|
||||
listStyleType: 'none',
|
||||
padding: 0,
|
||||
minHeight: '100%',
|
||||
}));
|
||||
|
||||
const StyledItem = styled('li')(({ theme }) => ({
|
||||
'& > [aria-pressed=true]': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
}));
|
||||
|
||||
export const FeatureMetricsChips = ({
|
||||
title,
|
||||
values,
|
||||
value,
|
||||
setValue,
|
||||
}: IFeatureMetricsChipsProps) => {
|
||||
const { classes: themeStyles } = useThemeStyles();
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
const onClick = (value: string) => () => {
|
||||
if (values.has(value)) {
|
||||
setValue(value);
|
||||
@ -33,19 +53,19 @@ export const FeatureMetricsChips = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className={styles.title}>{title}</h2>
|
||||
<ul className={styles.list}>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
<StyledList>
|
||||
{sortedValues.map(val => (
|
||||
<li key={val} className={styles.item}>
|
||||
<StyledItem key={val}>
|
||||
<Chip
|
||||
label={val}
|
||||
onClick={onClick(val)}
|
||||
aria-pressed={val === value}
|
||||
className={themeStyles.focusable}
|
||||
sx={focusable}
|
||||
/>
|
||||
</li>
|
||||
</StyledItem>
|
||||
))}
|
||||
</ul>
|
||||
</StyledList>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
item: {
|
||||
padding: theme.spacing(2),
|
||||
background: 'transparent',
|
||||
borderRadius: theme.spacing(2),
|
||||
textAlign: 'center',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: theme.spacing(4),
|
||||
},
|
||||
},
|
||||
title: {
|
||||
margin: 0,
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.fontWeight.thin,
|
||||
},
|
||||
value: {
|
||||
fontSize: '2.25rem',
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
text: {
|
||||
margin: '.5rem 0 0 0',
|
||||
padding: '1rem 0 0 0',
|
||||
borderTopWidth: 1,
|
||||
borderTopStyle: 'solid',
|
||||
borderTopColor: theme.palette.grey[300],
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.grey[800],
|
||||
},
|
||||
}));
|
@ -1,6 +1,5 @@
|
||||
import { calculatePercentage } from 'utils/calculatePercentage';
|
||||
import { useStyles } from './FeatureMetricsStats.styles';
|
||||
import { Grid } from '@mui/material';
|
||||
import { Grid, styled } from '@mui/material';
|
||||
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
||||
|
||||
export interface IFeatureMetricsStatsProps {
|
||||
@ -11,6 +10,38 @@ export interface IFeatureMetricsStatsProps {
|
||||
tableSectionId?: string;
|
||||
}
|
||||
|
||||
const StyledItem = styled('article')(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
background: 'transparent',
|
||||
borderRadius: theme.spacing(2),
|
||||
textAlign: 'center',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: theme.spacing(4),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledTitle = styled('h3')(({ theme }) => ({
|
||||
margin: 0,
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.fontWeight.thin,
|
||||
}));
|
||||
|
||||
const StyledValue = styled('p')(({ theme }) => ({
|
||||
fontSize: '2.25rem',
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
color: theme.palette.primary.main,
|
||||
}));
|
||||
|
||||
const StyledText = styled('p')(({ theme }) => ({
|
||||
margin: theme.spacing(1, 0, 0, 0),
|
||||
padding: theme.spacing(2, 0, 0, 0),
|
||||
borderTopWidth: '1px',
|
||||
borderTopStyle: 'solid',
|
||||
borderTopColor: theme.palette.dividerAlternative,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
export const FeatureMetricsStats = ({
|
||||
totalYes,
|
||||
totalNo,
|
||||
@ -18,8 +49,6 @@ export const FeatureMetricsStats = ({
|
||||
statsSectionId,
|
||||
tableSectionId,
|
||||
}: IFeatureMetricsStatsProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
const hoursSuffix =
|
||||
hoursBack === 1 ? 'in the last hour' : `in the last ${hoursBack} hours`;
|
||||
|
||||
@ -33,40 +62,40 @@ export const FeatureMetricsStats = ({
|
||||
component="section"
|
||||
>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<article className={styles.item}>
|
||||
<h3 className={styles.title}>Exposure</h3>
|
||||
<p className={styles.value}>
|
||||
<StyledItem>
|
||||
<StyledTitle>Exposure</StyledTitle>
|
||||
<StyledValue>
|
||||
<PrettifyLargeNumber value={totalYes} />
|
||||
</p>
|
||||
<p className={styles.text}>
|
||||
</StyledValue>
|
||||
<StyledText>
|
||||
Total exposure of the feature in the environment{' '}
|
||||
{hoursSuffix}.
|
||||
</p>
|
||||
</article>
|
||||
</StyledText>
|
||||
</StyledItem>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<article className={styles.item}>
|
||||
<h3 className={styles.title}>Exposure %</h3>
|
||||
<p className={styles.value}>
|
||||
<StyledItem>
|
||||
<StyledTitle>Exposure %</StyledTitle>
|
||||
<StyledValue>
|
||||
{calculatePercentage(totalYes + totalNo, totalYes)}%
|
||||
</p>
|
||||
<p className={styles.text}>
|
||||
</StyledValue>
|
||||
<StyledText>
|
||||
% total exposure of the feature in the environment{' '}
|
||||
{hoursSuffix}.
|
||||
</p>
|
||||
</article>
|
||||
</StyledText>
|
||||
</StyledItem>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<article className={styles.item}>
|
||||
<h3 className={styles.title}>Requests</h3>
|
||||
<p className={styles.value}>
|
||||
<StyledItem>
|
||||
<StyledTitle>Requests</StyledTitle>
|
||||
<StyledValue>
|
||||
<PrettifyLargeNumber value={totalYes + totalNo} />
|
||||
</p>
|
||||
<p className={styles.text}>
|
||||
</StyledValue>
|
||||
<StyledText>
|
||||
Total requests for the feature in the environment{' '}
|
||||
{hoursSuffix}.
|
||||
</p>
|
||||
</article>
|
||||
</StyledText>
|
||||
</StyledItem>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
featureId: {
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
}));
|
@ -1,17 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getCreateTogglePath } from 'utils/routePathHelpers';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { useStyles } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound.styles';
|
||||
import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
const StyledFeatureId = styled('strong')({
|
||||
wordBreak: 'break-all',
|
||||
});
|
||||
|
||||
export const FeatureNotFound = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { archivedFeatures } = useFeaturesArchive();
|
||||
const { classes: styles } = useStyles();
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
const createFeatureTogglePath = getCreateTogglePath(projectId, {
|
||||
name: featureId,
|
||||
@ -28,8 +29,7 @@ export const FeatureNotFound = () => {
|
||||
if (isArchived) {
|
||||
return (
|
||||
<p>
|
||||
The feature{' '}
|
||||
<strong className={styles.featureId}>{featureId}</strong> has
|
||||
The feature <StyledFeatureId>{featureId}</StyledFeatureId> has
|
||||
been archived. You can find it on the{' '}
|
||||
<Link to={`/projects/${projectId}/archive`}>
|
||||
project archive page
|
||||
@ -41,8 +41,7 @@ export const FeatureNotFound = () => {
|
||||
|
||||
return (
|
||||
<p>
|
||||
The feature{' '}
|
||||
<strong className={styles.featureId}>{featureId}</strong> does not
|
||||
The feature <StyledFeatureId>{featureId}</StyledFeatureId> does not
|
||||
exist. Would you like to{' '}
|
||||
<Link to={createFeatureTogglePath}>create it</Link>?
|
||||
</p>
|
||||
|
@ -1,9 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
dialogFormContent: {
|
||||
['& > *']: {
|
||||
margin: '0.5rem 0',
|
||||
},
|
||||
},
|
||||
}));
|
@ -2,7 +2,6 @@ import { styled, Typography } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import { useStyles } from './AddTagDialog.styles';
|
||||
import { trim } from 'component/common/util';
|
||||
import TagSelect from 'component/common/TagSelect/TagSelect';
|
||||
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||
@ -28,9 +27,14 @@ interface IDefaultTag {
|
||||
[index: string]: string;
|
||||
}
|
||||
|
||||
const StyledDialogFormContent = styled('section')(({ theme }) => ({
|
||||
['& > *']: {
|
||||
margin: '0.5rem 0',
|
||||
},
|
||||
}));
|
||||
|
||||
const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
||||
const DEFAULT_TAG: IDefaultTag = { type: 'simple', value: '' };
|
||||
const { classes: styles } = useStyles();
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { addTagToFeature, loading } = useFeatureApi();
|
||||
const { tags, refetch } = useTags(featureId);
|
||||
@ -106,7 +110,7 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
||||
Tags allow you to group features together
|
||||
</Typography>
|
||||
<form id={formId} onSubmit={onSubmit}>
|
||||
<section className={styles.dialogFormContent}>
|
||||
<StyledDialogFormContent>
|
||||
<TagSelect
|
||||
autoFocus
|
||||
name="type"
|
||||
@ -126,7 +130,7 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
||||
}
|
||||
required
|
||||
/>
|
||||
</section>
|
||||
</StyledDialogFormContent>
|
||||
</form>
|
||||
</>
|
||||
</Dialogue>
|
||||
|
@ -1,26 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
mainContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: `calc(100% - (350px + 1rem))`,
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
trafficContainer: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
marginTop: '1rem',
|
||||
},
|
||||
},
|
||||
}));
|
@ -1,5 +1,4 @@
|
||||
import FeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMetaData';
|
||||
import { useStyles } from './FeatureOverview.styles';
|
||||
import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
|
||||
import FeatureOverviewEnvSwitches from './FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitches';
|
||||
import { Routes, Route, useNavigate } from 'react-router-dom';
|
||||
@ -15,10 +14,27 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { FeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel';
|
||||
import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledMainContent = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: `calc(100% - (350px + 1rem))`,
|
||||
[theme.breakpoints.down(1000)]: {
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
|
||||
const FeatureOverview = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { classes: styles } = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
@ -29,7 +45,7 @@ const FeatureOverview = () => {
|
||||
usePageTitle(featureId);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<StyledContainer>
|
||||
<div>
|
||||
<FeatureOverviewMetaData />
|
||||
<ConditionallyRender
|
||||
@ -43,9 +59,9 @@ const FeatureOverview = () => {
|
||||
elseShow={<FeatureOverviewEnvSwitches />}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
<StyledMainContent>
|
||||
<FeatureOverviewEnvironments />
|
||||
</div>
|
||||
</StyledMainContent>
|
||||
<Routes>
|
||||
<Route
|
||||
path="strategies/create"
|
||||
@ -72,7 +88,7 @@ const FeatureOverview = () => {
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,58 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
boxShadow: 'none',
|
||||
display: 'flex',
|
||||
},
|
||||
header: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
marginBottom: '1rem',
|
||||
},
|
||||
toggleInfoContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
toolbarContainer: {
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
},
|
||||
innerContainer: {
|
||||
padding: theme.spacing(2, 4, 2, 2),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
[theme.breakpoints.down(500)]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
separator: {
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.grey[200],
|
||||
height: '1px',
|
||||
},
|
||||
tabContainer: {
|
||||
padding: '0 2rem',
|
||||
},
|
||||
tabButton: {
|
||||
textTransform: 'none',
|
||||
width: 'auto',
|
||||
fontSize: '1rem',
|
||||
padding: '0 !important',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
minWidth: 160,
|
||||
},
|
||||
},
|
||||
featureViewHeader: {
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
fontWeight: 'normal',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
statusContainer: {
|
||||
marginLeft: '0.5rem',
|
||||
},
|
||||
}));
|
@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { Tab, Tabs, useMediaQuery } from '@mui/material';
|
||||
import { styled, Tab, Tabs, useMediaQuery } from '@mui/material';
|
||||
import { Archive, FileCopy, Label, WatchLater } from '@mui/icons-material';
|
||||
import {
|
||||
Link,
|
||||
@ -20,7 +20,6 @@ import FeatureLog from './FeatureLog/FeatureLog';
|
||||
import FeatureOverview from './FeatureOverview/FeatureOverview';
|
||||
import FeatureVariants from './FeatureVariants/FeatureVariants';
|
||||
import { FeatureMetrics } from './FeatureMetrics/FeatureMetrics';
|
||||
import { useStyles } from './FeatureView.styles';
|
||||
import { FeatureSettings } from './FeatureSettings/FeatureSettings';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
@ -33,6 +32,60 @@ import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/Feat
|
||||
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
|
||||
import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton';
|
||||
|
||||
const StyledHeader = styled('div')(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledInnerContainer = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(2, 4, 2, 2),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
[theme.breakpoints.down(500)]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledToggleInfoContainer = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const StyledFeatureViewHeader = styled('h1')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
fontWeight: 'normal',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
wordBreak: 'break-all',
|
||||
}));
|
||||
|
||||
const StyledToolbarContainer = styled('div')({
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
const StyledSeparator = styled('div')(({ theme }) => ({
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.tertiary.light,
|
||||
height: '1px',
|
||||
}));
|
||||
|
||||
const StyledTabContainer = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(0, 4),
|
||||
}));
|
||||
|
||||
const StyledTabButton = styled(Tab)(({ theme }) => ({
|
||||
textTransform: 'none',
|
||||
width: 'auto',
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
padding: '0 !important',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
minWidth: 160,
|
||||
},
|
||||
}));
|
||||
|
||||
export const FeatureView = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
@ -50,7 +103,6 @@ export const FeatureView = () => {
|
||||
featureId
|
||||
);
|
||||
|
||||
const { classes: styles } = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const ref = useLoading(loading);
|
||||
@ -98,23 +150,23 @@ export const FeatureView = () => {
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.innerContainer}>
|
||||
<div className={styles.toggleInfoContainer}>
|
||||
<StyledHeader>
|
||||
<StyledInnerContainer>
|
||||
<StyledToggleInfoContainer>
|
||||
<FavoriteIconButton
|
||||
onClick={onFavorite}
|
||||
isFavorite={feature?.favorite}
|
||||
/>
|
||||
<h1 className={styles.featureViewHeader} data-loading>
|
||||
<StyledFeatureViewHeader data-loading>
|
||||
{feature.name}{' '}
|
||||
</h1>
|
||||
</StyledFeatureViewHeader>
|
||||
<ConditionallyRender
|
||||
condition={!smallScreen}
|
||||
show={<FeatureStatusChip stale={feature?.stale} />}
|
||||
/>
|
||||
</div>
|
||||
</StyledToggleInfoContainer>
|
||||
|
||||
<div className={styles.toolbarContainer}>
|
||||
<StyledToolbarContainer>
|
||||
<PermissionIconButton
|
||||
permission={CREATE_FEATURE}
|
||||
projectId={projectId}
|
||||
@ -158,27 +210,26 @@ export const FeatureView = () => {
|
||||
>
|
||||
<Label />
|
||||
</PermissionIconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.separator} />
|
||||
<div className={styles.tabContainer}>
|
||||
</StyledToolbarContainer>
|
||||
</StyledInnerContainer>
|
||||
<StyledSeparator />
|
||||
<StyledTabContainer>
|
||||
<Tabs
|
||||
value={activeTab.path}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
>
|
||||
{tabData.map(tab => (
|
||||
<Tab
|
||||
<StyledTabButton
|
||||
key={tab.title}
|
||||
label={tab.title}
|
||||
value={tab.path}
|
||||
onClick={() => navigate(tab.path)}
|
||||
className={styles.tabButton}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</StyledTabContainer>
|
||||
</StyledHeader>
|
||||
<Routes>
|
||||
<Route path="metrics" element={<FeatureMetrics />} />
|
||||
<Route path="logs" element={<FeatureLog />} />
|
||||
|
@ -22,7 +22,7 @@ import { ReactComponent as UnleashLogoWhite } from 'assets/img/logoWithWhiteText
|
||||
|
||||
import { DrawerMenu } from './DrawerMenu/DrawerMenu';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { flexRow, focusable, useThemeStyles } from 'themes/themeStyles';
|
||||
import { flexRow, focusable } from 'themes/themeStyles';
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { IPermission } from 'interfaces/user';
|
||||
import { NavigationMenu } from './NavigationMenu/NavigationMenu';
|
||||
@ -97,9 +97,7 @@ const styledIconProps = (theme: Theme) => ({
|
||||
color: theme.palette.neutral.main,
|
||||
});
|
||||
|
||||
const StyledLink = styled(Link)(({ theme }) => ({
|
||||
...focusable(theme),
|
||||
}));
|
||||
const StyledLink = styled(Link)(({ theme }) => focusable(theme));
|
||||
|
||||
const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
||||
...focusable(theme),
|
||||
@ -158,9 +156,7 @@ const Header: VFC = () => {
|
||||
<StyledContainer>
|
||||
<Tooltip title="Menu" arrow>
|
||||
<IconButton
|
||||
sx={theme => ({
|
||||
color: theme.palette.text.tertiaryContrast,
|
||||
})}
|
||||
sx={{ color: theme => theme.palette.text.primary }}
|
||||
onClick={toggleDrawer}
|
||||
aria-controls="header-drawer"
|
||||
aria-expanded={openDrawer}
|
||||
|
Loading…
Reference in New Issue
Block a user