1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

Refactored to single responsibility components

This commit is contained in:
andreas-unleash 2022-07-22 09:40:08 +03:00
parent c16272ec26
commit f6b6aa0ae8
12 changed files with 389 additions and 261 deletions

View File

@ -17,8 +17,8 @@ export const useStyles = makeStyles()(theme => ({
fill: '#fff',
},
accordion: {
border: `1px solid ${theme.palette.grey[300]}`,
borderRadius: '5px',
border: `1px solid ${theme.palette.grey[400]}`,
borderRadius: '8px',
backgroundColor: '#fff',
boxShadow: 'none',
margin: 0,

View File

@ -64,29 +64,37 @@ export const RestrictiveLegalValues = ({
};
return (
<>
<ConstraintFormHeader>
Select values from a predefined set
</ConstraintFormHeader>
<ConstraintValueSearch filter={filter} setFilter={setFilter} />
{filteredValues.map(match => (
<LegalValueLabel
key={match.value}
legal={match}
control={
<Checkbox
checked={Boolean(valuesMap[match.value])}
onChange={() => onChange(match.value)}
name={match.value}
color="primary"
<ConditionallyRender
condition={Boolean(values.length > 500)}
show={
<>
<ConstraintFormHeader>
Select values from a predefined set
</ConstraintFormHeader>
<ConstraintValueSearch
filter={filter}
setFilter={setFilter}
/>
{filteredValues.map(match => (
<LegalValueLabel
key={match.value}
legal={match}
control={
<Checkbox
checked={Boolean(valuesMap[match.value])}
onChange={() => onChange(match.value)}
name={match.value}
color="primary"
/>
}
/>
}
/>
))}
<ConditionallyRender
condition={Boolean(error)}
show={<p className={styles.error}>{error}</p>}
/>
</>
))}
<ConditionallyRender
condition={Boolean(error)}
show={<p className={styles.error}>{error}</p>}
/>
</>
}
/>
);
};

View File

@ -36,7 +36,15 @@ export const SingleLegalValue = ({
<ConstraintFormHeader>
Add a single {type.toLowerCase()} value
</ConstraintFormHeader>
<ConstraintValueSearch filter={filter} setFilter={setFilter} />
<ConditionallyRender
condition={Boolean(legalValues.length > 500)}
show={
<ConstraintValueSearch
filter={filter}
setFilter={setFilter}
/>
}
/>
<ConditionallyRender
condition={Boolean(legalValues.length)}
show={

View File

@ -4,7 +4,6 @@ import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccord
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon';
import { Delete, Edit } from '@mui/icons-material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import {
dateOperators,
@ -21,9 +20,9 @@ import {
operatorsForContext,
CURRENT_TIME_CONTEXT_FIELD,
} from 'utils/operatorsForContext';
import { IconButton, Tooltip } from '@mui/material';
import { InvertedOperatorButton } from '../StyledToggleButton/InvertedOperatorButton/InvertedOperatorButton';
import { CaseSensitiveButton } from '../StyledToggleButton/CaseSensitiveButton/CaseSensitiveButton';
import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions';
interface IConstraintAccordionViewHeader {
localConstraint: IConstraint;
@ -101,13 +100,6 @@ export const ConstraintAccordionEditHeader = ({
}
};
const onDeleteClick =
onDelete &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onDelete();
});
return (
<div className={styles.headerContainer}>
<ConstraintIcon />
@ -155,16 +147,7 @@ export const ConstraintAccordionEditHeader = ({
</p>
}
/>
<div className={styles.headerActions}>
<IconButton type="button" disabled>
<Edit />
</IconButton>
<Tooltip title="Delete constraint" arrow>
<IconButton type="button" onClick={onDeleteClick}>
<Delete />
</IconButton>
</Tooltip>
</div>
<ConstraintAccordionHeaderActions onDelete={onDelete} />
</div>
);
};

View File

@ -0,0 +1,63 @@
import React from 'react';
import { ConditionallyRender } from '../../ConditionallyRender/ConditionallyRender';
import { IconButton, Tooltip } from '@mui/material';
import { Delete, Edit } from '@mui/icons-material';
import { useStyles } from '../ConstraintAccordion.styles';
interface ConstraintAccordionHeaderActionsProps {
onDelete?: () => void;
onEdit?: () => void;
}
export const ConstraintAccordionHeaderActions = ({
onEdit,
onDelete,
}: ConstraintAccordionHeaderActionsProps) => {
const { classes: styles } = useStyles();
const onEditClick =
onEdit &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onEdit();
});
const onDeleteClick =
onDelete &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onDelete();
});
return (
<div className={styles.headerActions}>
<ConditionallyRender
condition={Boolean(onEditClick)}
show={() => (
<Tooltip title="Edit constraint" arrow>
<IconButton
type="button"
onClick={onEditClick}
disabled={!onEdit}
>
<Edit />
</IconButton>
</Tooltip>
)}
/>
<ConditionallyRender
condition={Boolean(onDeleteClick)}
show={() => (
<Tooltip title="Delete constraint" arrow>
<IconButton
type="button"
onClick={onDeleteClick}
disabled={!onDelete}
>
<Delete />
</IconButton>
</Tooltip>
)}
/>
</div>
);
};

View File

@ -1,72 +1,10 @@
import { Chip, IconButton, Tooltip, styled } from '@mui/material';
import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon';
import { Delete, Edit } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy';
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import React, { useEffect, useRef, useState } from 'react';
import { formatConstraintValue } from 'utils/formatConstraintValue';
import { useLocationSettings } from 'hooks/useLocationSettings';
import { ConstraintOperator } from 'component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator';
import classnames from 'classnames';
import { getTextWidth } from '../../utils';
import { ReactComponent as NegatedIcon } from 'assets/icons/24_Negator.svg';
import { ReactComponent as CaseSensitive } from 'assets/icons/24_Text format.svg';
import { stringOperators } from 'constants/operators';
import { oneOf } from 'utils/oneOf';
const StyledHeaderText = styled('span')(({ theme }) => ({
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
maxWidth: '100px',
minWidth: '100px',
marginRight: '10px',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
textAlign: 'center',
padding: theme.spacing(1, 0),
marginRight: 'inherit',
maxWidth: 'inherit',
},
}));
const StyledValuesSpan = styled('span')(({ theme }) => ({
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
margin: theme.spacing(1, 0),
textAlign: 'center',
},
}));
const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
[theme.breakpoints.down(710)]: {
margin: theme.spacing(1, 0),
},
}));
const StyledIconWrapper = styled('div')<{
marginRight?: string;
marginTop?: string;
}>(({ theme, marginRight, marginTop }) => ({
backgroundColor: theme.palette.grey[200],
width: 28,
height: 47,
display: 'inline-flex',
justifyContent: 'center',
padding: '10px 0',
color: theme.palette.primary.main,
marginRight: marginRight ? marginRight : '0.75rem',
marginTop: marginTop ? marginTop : 0,
}));
import React from 'react';
import { ConstraintAccordionViewHeaderInfo } from './ConstraintAccordionViewHeaderInfo/ConstraintAccordionViewHeaderInfo';
import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions';
interface IConstraintAccordionViewHeaderProps {
compact: boolean;
@ -88,161 +26,21 @@ export const ConstraintAccordionViewHeader = ({
expanded,
}: IConstraintAccordionViewHeaderProps) => {
const { classes: styles } = useStyles();
const { locationSettings } = useLocationSettings();
const [width, setWidth] = useState(0);
const [textWidth, setTextWidth] = useState(0);
const [expandable, setExpandable] = useState(false);
const elementRef = useRef<HTMLElement>(null);
const onEditClick =
onEdit &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onEdit();
});
const onDeleteClick =
onDelete &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onDelete();
});
useEffect(() => {
if (elementRef && elementRef.current != null) {
setTextWidth(
Math.round(getTextWidth(elementRef.current.innerText) / 2) // 2 lines
);
setWidth(elementRef.current.clientWidth);
}
}, []);
useEffect(() => {
if (textWidth && width) {
setExpandable(textWidth > width);
allowExpand(textWidth > width);
}
}, [textWidth, width, allowExpand]);
return (
<div className={styles.headerContainer}>
<ConstraintIcon />
<div className={styles.headerMetaInfo}>
<Tooltip title={constraint.contextName} arrow>
<StyledHeaderText>
{constraint.contextName}
</StyledHeaderText>
</Tooltip>
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender
condition={Boolean(constraint.inverted)}
show={
<Tooltip title={'Operator is negated'} arrow>
<StyledIconWrapper marginRight={'0'}>
<NegatedIcon />
</StyledIconWrapper>
</Tooltip>
}
/>
<div className={styles.headerConstraintContainer}>
<ConstraintOperator constraint={constraint} />
</div>
</div>
<ConditionallyRender
condition={singleValue}
show={
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender
condition={
!Boolean(constraint.caseInsensitive) &&
oneOf(stringOperators, constraint.operator)
}
show={
<Tooltip
title="Case sensitive is active"
arrow
>
<StyledIconWrapper>
<CaseSensitive />{' '}
</StyledIconWrapper>
</Tooltip>
}
/>
<StyledSingleValueChip
label={formatConstraintValue(
constraint,
locationSettings
)}
/>
</div>
}
elseShow={
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender
condition={
!Boolean(constraint.caseInsensitive) &&
oneOf(stringOperators, constraint.operator)
}
show={
<Tooltip
title="Case sensitive is active"
arrow
>
<StyledIconWrapper marginTop={'7px'}>
<CaseSensitive />{' '}
</StyledIconWrapper>
</Tooltip>
}
/>
<div className={styles.headerValuesContainer}>
<StyledValuesSpan ref={elementRef}>
{constraint?.values
?.map(value => value)
.join(', ')}
</StyledValuesSpan>
<ConditionallyRender
condition={expandable}
show={
<p
className={classnames(
styles.headerValuesExpand,
'valuesExpandLabel'
)}
>
{!expanded
? `Expand to view all (
${constraint?.values?.length})`
: 'Collapse to view less'}
</p>
}
/>
</div>
</div>
}
/>
</div>
<div className={styles.headerActions}>
<ConditionallyRender
condition={Boolean(onEditClick)}
show={() => (
<Tooltip title="Edit constraint" arrow>
<IconButton type="button" onClick={onEditClick}>
<Edit />
</IconButton>
</Tooltip>
)}
/>
<ConditionallyRender
condition={Boolean(onDeleteClick)}
show={() => (
<Tooltip title="Delete constraint" arrow>
<IconButton type="button" onClick={onDeleteClick}>
<Delete />
</IconButton>
</Tooltip>
)}
/>
</div>
<ConstraintAccordionViewHeaderInfo
compact={compact}
constraint={constraint}
singleValue={singleValue}
allowExpand={allowExpand}
expanded={expanded}
/>
<ConstraintAccordionHeaderActions
onEdit={onEdit}
onDelete={onDelete}
/>
</div>
);
};

View File

@ -0,0 +1,65 @@
import { styled, Tooltip } from '@mui/material';
import { ConstraintViewHeaderOperator } from '../ConstraintViewHeaderOperator/ConstraintViewHeaderOperator';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { ContraintAccordionViewHeaderSingleValue } from '../ContraintAccordionViewHeaderSingleValue/ContraintAccordionViewHeaderSingleValue';
import { ConstraintAccordionViewHeaderMultipleValues } from '../ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues';
import React from 'react';
import { IConstraint } from '../../../../../../interfaces/strategy';
import { useStyles } from '../../../ConstraintAccordion.styles';
const StyledHeaderText = styled('span')(({ theme }) => ({
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
maxWidth: '100px',
minWidth: '100px',
marginRight: '10px',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
textAlign: 'center',
padding: theme.spacing(1, 0),
marginRight: 'inherit',
maxWidth: 'inherit',
},
}));
interface ConstraintAccordionViewHeaderMetaInfoProps {
constraint: IConstraint;
singleValue: boolean;
expanded: boolean;
allowExpand: (shouldExpand: boolean) => void;
}
export const ConstraintAccordionViewHeaderInfo = ({
constraint,
singleValue,
allowExpand,
expanded,
}: ConstraintAccordionViewHeaderMetaInfoProps) => {
const { classes: styles } = useStyles();
return (
<div className={styles.headerMetaInfo}>
<Tooltip title={constraint.contextName} arrow>
<StyledHeaderText>{constraint.contextName}</StyledHeaderText>
</Tooltip>
<ConstraintViewHeaderOperator constraint={constraint} />
<ConditionallyRender
condition={singleValue}
show={
<ContraintAccordionViewHeaderSingleValue
constraint={constraint}
/>
}
elseShow={
<ConstraintAccordionViewHeaderMultipleValues
constraint={constraint}
expanded={expanded}
allowExpand={allowExpand}
/>
}
/>
</div>
);
};

View File

@ -0,0 +1,36 @@
import { IConstraint } from '../../../../../../interfaces/strategy';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { Tooltip } from '@mui/material';
import { ReactComponent as NegatedIcon } from '../../../../../../assets/icons/24_Negator.svg';
import { ConstraintOperator } from '../../../ConstraintOperator/ConstraintOperator';
import React from 'react';
import { useStyles } from '../../../ConstraintAccordion.styles';
import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
interface ConstraintViewHeaderOperatorProps {
constraint: IConstraint;
}
export const ConstraintViewHeaderOperator = ({
constraint,
}: ConstraintViewHeaderOperatorProps) => {
const { classes: styles } = useStyles();
return (
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender
condition={Boolean(constraint.inverted)}
show={
<Tooltip title={'Operator is negated'} arrow>
<StyledIconWrapper marginRight={'0'}>
<NegatedIcon />
</StyledIconWrapper>
</Tooltip>
}
/>
<div className={styles.headerConstraintContainer}>
<ConstraintOperator constraint={constraint} />
</div>
</div>
);
};

View File

@ -0,0 +1,102 @@
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { oneOf } from '../../../../../../utils/oneOf';
import { stringOperators } from '../../../../../../constants/operators';
import { styled, Tooltip } from '@mui/material';
import { ReactComponent as CaseSensitive } from '../../../../../../assets/icons/24_Text format.svg';
import React, { useEffect, useRef, useState } from 'react';
import classnames from 'classnames';
import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
import { IConstraint } from '../../../../../../interfaces/strategy';
import { useStyles } from '../../../ConstraintAccordion.styles';
import { getTextWidth } from '../../../helpers';
const StyledValuesSpan = styled('span')(({ theme }) => ({
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
margin: theme.spacing(1, 0),
textAlign: 'center',
},
}));
interface ConstraintSingleValueProps {
constraint: IConstraint;
expanded: boolean;
allowExpand: (shouldExpand: boolean) => void;
}
export const ConstraintAccordionViewHeaderMultipleValues = ({
constraint,
expanded,
allowExpand,
}: ConstraintSingleValueProps) => {
const { classes: styles } = useStyles();
const elementRef = useRef<HTMLElement>(null);
const [width, setWidth] = useState(0);
const [textWidth, setTextWidth] = useState(0);
const [expandable, setExpandable] = useState(false);
useEffect(() => {
if (elementRef && elementRef.current != null) {
setTextWidth(
Math.round(getTextWidth(elementRef.current.innerText) / 2) // 2 lines
);
setWidth(elementRef.current.clientWidth);
}
}, []);
useEffect(() => {
if (textWidth && width) {
setExpandable(textWidth > width);
}
}, [textWidth, width]);
useEffect(() => {
allowExpand(expandable);
}, [expandable, allowExpand]);
return (
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender
condition={
!Boolean(constraint.caseInsensitive) &&
oneOf(stringOperators, constraint.operator)
}
show={
<Tooltip title="Case sensitive is active" arrow>
<StyledIconWrapper marginTop={'7px'}>
<CaseSensitive />{' '}
</StyledIconWrapper>
</Tooltip>
}
/>
<div className={styles.headerValuesContainer}>
<StyledValuesSpan ref={elementRef}>
{constraint?.values?.map(value => value).join(', ')}
</StyledValuesSpan>
<ConditionallyRender
condition={expandable}
show={
<p
className={classnames(
styles.headerValuesExpand,
'valuesExpandLabel'
)}
>
{!expanded
? `Expand to view all (
${constraint?.values?.length})`
: 'Collapse to view less'}
</p>
}
/>
</div>
</div>
);
};

View File

@ -0,0 +1,49 @@
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { oneOf } from '../../../../../../utils/oneOf';
import { stringOperators } from '../../../../../../constants/operators';
import { Chip, styled, Tooltip } from '@mui/material';
import { ReactComponent as CaseSensitive } from '../../../../../../assets/icons/24_Text format.svg';
import { formatConstraintValue } from '../../../../../../utils/formatConstraintValue';
import React from 'react';
import { useStyles } from '../../../ConstraintAccordion.styles';
import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
import { IConstraint } from '../../../../../../interfaces/strategy';
import { useLocationSettings } from '../../../../../../hooks/useLocationSettings';
const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
[theme.breakpoints.down(710)]: {
margin: theme.spacing(1, 0),
},
}));
interface ConstraintSingleValueProps {
constraint: IConstraint;
}
export const ContraintAccordionViewHeaderSingleValue = ({
constraint,
}: ConstraintSingleValueProps) => {
const { locationSettings } = useLocationSettings();
const { classes: styles } = useStyles();
return (
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender
condition={
!Boolean(constraint.caseInsensitive) &&
oneOf(stringOperators, constraint.operator)
}
show={
<Tooltip title="Case sensitive is active" arrow>
<StyledIconWrapper>
<CaseSensitive />{' '}
</StyledIconWrapper>
</Tooltip>
}
/>
<StyledSingleValueChip
label={formatConstraintValue(constraint, locationSettings)}
/>
</div>
);
};

View File

@ -0,0 +1,16 @@
import { styled } from '@mui/material';
export const StyledIconWrapper = styled('div')<{
marginRight?: string;
marginTop?: string;
}>(({ theme, marginRight, marginTop }) => ({
backgroundColor: theme.palette.grey[200],
width: 28,
height: 47,
display: 'inline-flex',
justifyContent: 'center',
padding: '10px 0',
color: theme.palette.primary.main,
marginRight: marginRight ? marginRight : '0.75rem',
marginTop: marginTop ? marginTop : 0,
}));