1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-26 01:17:00 +02:00

Fix/constraint card adjustments (#1154)

* style fixes

* Constraint card styling adjustments

* Style Fixes

* lint and fmt

* lint and fmt

* Changed the way the expandable property is evaluated to use the text.length

Co-authored-by: Tymoteusz Czech <tymek+gpg@getunleash.ai>
This commit is contained in:
andreas-unleash 2022-07-27 14:50:47 +03:00 committed by GitHub
parent c70b38a62a
commit c3e9b49e12
20 changed files with 121 additions and 103 deletions

View File

@ -12,6 +12,9 @@ export const useStyles = makeStyles()(theme => ({
marginBottom: '1rem',
marginRight: 0,
},
[theme.breakpoints.between(1101, 1365)]: {
marginRight: '8px',
},
},
constraintIcon: {
fill: '#fff',
@ -59,6 +62,7 @@ export const useStyles = makeStyles()(theme => ({
},
headerValuesExpand: {
fontSize: theme.fontSizes.smallBody,
marginTop: '4px',
color: theme.palette.primary.dark,
[theme.breakpoints.down(710)]: {
textAlign: 'center',
@ -71,6 +75,10 @@ export const useStyles = makeStyles()(theme => ({
[theme.breakpoints.down(650)]: {
paddingRight: 0,
},
[theme.breakpoints.between(1101, 1365)]: {
minWidth: '152px',
paddingRight: '0.5rem',
},
},
editingBadge: {
borderRadius: theme.shape.borderRadiusExtraLarge,
@ -108,6 +116,10 @@ export const useStyles = makeStyles()(theme => ({
headerSelect: {
marginRight: '1rem',
width: '200px',
[theme.breakpoints.between(1101, 1365)]: {
width: '170px',
marginRight: '8px',
},
},
chip: {
margin: '0 0.5rem 0.5rem 0',

View File

@ -5,6 +5,7 @@ export const StyledToggleButtonOff = styled(IconButton)(({ theme }) => ({
width: '28px',
minWidth: '28px',
maxWidth: '28px',
height: 'auto',
backgroundColor: theme.palette.tertiary.background,
borderRadius: theme.shape.borderRadius,
padding: '0 1px 0',
@ -12,6 +13,10 @@ export const StyledToggleButtonOff = styled(IconButton)(({ theme }) => ({
'&:hover': {
background: theme.palette.tertiary.contrast[300],
},
[theme.breakpoints.between(1101, 1365)]: {
marginRight: '0.5rem',
alignItems: 'center',
},
}));
export const StyledToggleButtonOn = styled(IconButton)(({ theme }) => ({
@ -27,4 +32,8 @@ export const StyledToggleButtonOn = styled(IconButton)(({ theme }) => ({
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
},
[theme.breakpoints.between(1101, 1365)]: {
marginRight: '0.5rem',
alignItems: 'center',
},
}));

View File

@ -11,12 +11,18 @@ export const useStyles = makeStyles()(theme => ({
[theme.breakpoints.down(860)]: {
display: 'none',
},
marginLeft: '0.75rem',
},
helpWrapper: {
marginLeft: '12px',
height: '24px',
},
addCustomLabel: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'start',
margin: '0.75rem 0',
margin: '0.75rem 0 ',
},
customConstraintLabel: {
color: theme.palette.text.secondary,
},
}));

View File

@ -1,6 +1,6 @@
import React, { forwardRef, Fragment, useImperativeHandle } from 'react';
import React, { forwardRef, useImperativeHandle } from 'react';
import { Button, Tooltip } from '@mui/material';
import { Help } from '@mui/icons-material';
import { HelpOutline } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy';
import { ConstraintAccordion } from 'component/common/ConstraintAccordion/ConstraintAccordion';
import produce from 'immer';
@ -10,7 +10,6 @@ import { objectId } from 'utils/objectId';
import { useStyles } from './ConstraintAccordionList.styles';
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
interface IConstraintAccordionListProps {
constraints: IConstraint[];
@ -100,13 +99,37 @@ export const ConstraintAccordionList = forwardRef<
return (
<div className={styles.container} id={constraintAccordionListId}>
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<p className={styles.customConstraintLabel}>
Custom constraints
</p>
}
/>
{constraints.map((constraint, index) => (
<ConstraintAccordion
key={objectId(constraint)}
constraint={constraint}
onEdit={onEdit && onEdit.bind(null, constraint)}
onCancel={onCancel.bind(null, index)}
onDelete={onRemove && onRemove.bind(null, index)}
onSave={onSave && onSave.bind(null, index)}
editing={Boolean(state.get(constraint)?.editing)}
compact
/>
))}
<ConditionallyRender
condition={Boolean(showCreateButton && onAdd)}
show={
<div>
<div className={styles.addCustomLabel}>
<p>Add any number of custom constraints</p>
<Tooltip title="Help" arrow>
<Tooltip
title="Help"
arrow
className={styles.helpWrapper}
>
<a
href={
'https://docs.getunleash.io/advanced/strategy_constraints'
@ -114,7 +137,7 @@ export const ConstraintAccordionList = forwardRef<
target="_blank"
rel="noopener noreferrer"
>
<Help className={styles.help} />
<HelpOutline className={styles.help} />
</a>
</Tooltip>
</div>
@ -123,30 +146,13 @@ export const ConstraintAccordionList = forwardRef<
onClick={onAdd}
variant="outlined"
color="secondary"
sx={{ mb: 2 }}
>
Add custom constraint
</Button>
</div>
}
/>
{constraints.map((constraint, index) => (
<Fragment key={`${constraint.contextName}-${index}`}>
<ConditionallyRender
condition={index > 0}
show={<StrategySeparator text="AND" />}
/>
<ConstraintAccordion
key={objectId(constraint)}
constraint={constraint}
onEdit={onEdit && onEdit.bind(null, constraint)}
onCancel={onCancel.bind(null, index)}
onDelete={onRemove && onRemove.bind(null, index)}
onSave={onSave && onSave.bind(null, index)}
editing={Boolean(state.get(constraint)?.editing)}
compact
/>
</Fragment>
))}
</div>
);
});

View File

@ -42,12 +42,17 @@ export const ConstraintAccordionView = ({
className={styles.accordion}
classes={{ root: styles.accordionRoot }}
expanded={expanded}
sx={{ cursor: expandable ? 'pointer' : 'default' }}
>
<AccordionSummary
classes={{ root: styles.summary }}
expandIcon={null}
onClick={handleClick}
sx={{
cursor: expandable ? 'pointer' : 'default!important',
'&:hover': {
cursor: expandable ? 'pointer' : 'default!important',
},
}}
>
<ConstraintAccordionViewHeader
constraint={constraint}

View File

@ -1,12 +1,7 @@
import { ImportExportOutlined, TextFormatOutlined } from '@mui/icons-material';
import { stringOperators } from 'constants/operators';
import { IConstraint } from 'interfaces/strategy';
import { oneOf } from 'utils/oneOf';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
import { formatConstraintValue } from 'utils/formatConstraintValue';
import { useLocationSettings } from 'hooks/useLocationSettings';
import { useStyles as useAccordionStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
import { useStyles } from './ConstraintAccordionViewBody.style';
import { MultipleValues } from './MultipleValues/MultipleValues';
import { SingleValue } from './SingleValue/SingleValue';
@ -17,35 +12,11 @@ interface IConstraintAccordionViewBodyProps {
export const ConstraintAccordionViewBody = ({
constraint,
}: IConstraintAccordionViewBodyProps) => {
const { classes: styles } = useAccordionStyles();
const { classes } = useStyles();
const { classes: styles } = useStyles();
const { locationSettings } = useLocationSettings();
return (
<div>
<ConditionallyRender
condition={
oneOf(stringOperators, constraint.operator) &&
Boolean(constraint.caseInsensitive)
}
show={
<p className={classes.settingsParagraph}>
<TextFormatOutlined className={styles.settingsIcon} />{' '}
Case insensitive setting is active
</p>
}
/>
<ConditionallyRender
condition={Boolean(constraint.inverted)}
show={
<p className={classes.settingsParagraph}>
<ImportExportOutlined className={styles.settingsIcon} />{' '}
Operator is negated
</p>
}
/>
<div className={styles.valuesContainer}>
<MultipleValues values={constraint.values} />
<SingleValue

View File

@ -1,7 +1,7 @@
import { styled, Tooltip } from '@mui/material';
import { ConstraintViewHeaderOperator } from '../ConstraintViewHeaderOperator/ConstraintViewHeaderOperator';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { ContraintAccordionViewHeaderSingleValue } from '../ContraintAccordionViewHeaderSingleValue/ContraintAccordionViewHeaderSingleValue';
import { ConstraintAccordionViewHeaderSingleValue } from '../ContraintAccordionViewHeaderSingleValue/ConstraintAccordionViewHeaderSingleValue';
import { ConstraintAccordionViewHeaderMultipleValues } from '../ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues';
import React from 'react';
import { IConstraint } from '../../../../../../interfaces/strategy';
@ -50,8 +50,9 @@ export const ConstraintAccordionViewHeaderInfo = ({
<ConditionallyRender
condition={singleValue}
show={
<ContraintAccordionViewHeaderSingleValue
<ConstraintAccordionViewHeaderSingleValue
constraint={constraint}
allowExpand={allowExpand}
/>
}
elseShow={
@ -59,6 +60,7 @@ export const ConstraintAccordionViewHeaderInfo = ({
constraint={constraint}
expanded={expanded}
allowExpand={allowExpand}
maxLength={112}
/>
}
/>

View File

@ -3,12 +3,11 @@ 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 React, { useEffect, useMemo, 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',
@ -27,6 +26,7 @@ const StyledValuesSpan = styled('span')(({ theme }) => ({
interface ConstraintSingleValueProps {
constraint: IConstraint;
expanded: boolean;
maxLength: number;
allowExpand: (shouldExpand: boolean) => void;
}
@ -34,33 +34,22 @@ export const ConstraintAccordionViewHeaderMultipleValues = ({
constraint,
expanded,
allowExpand,
maxLength,
}: 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);
}
}, []);
const text = useMemo(() => {
return constraint?.values?.map(value => value).join(', ');
}, [constraint]);
useEffect(() => {
if (textWidth && width) {
setExpandable(textWidth > width);
if (text) {
allowExpand((text?.length ?? 0) > maxLength);
setExpandable((text?.length ?? 0) > maxLength);
}
}, [textWidth, width]);
useEffect(() => {
allowExpand(expandable);
}, [expandable, allowExpand]);
}, [text, maxLength, allowExpand, setExpandable]);
return (
<div className={styles.headerValuesContainerWrapper}>
@ -78,9 +67,7 @@ export const ConstraintAccordionViewHeaderMultipleValues = ({
}
/>
<div className={styles.headerValuesContainer}>
<StyledValuesSpan ref={elementRef}>
{constraint?.values?.map(value => value).join(', ')}
</StyledValuesSpan>
<StyledValuesSpan>{text}</StyledValuesSpan>
<ConditionallyRender
condition={expandable}
show={

View File

@ -1,3 +1,4 @@
import React, { useEffect } from 'react';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { oneOf } from '../../../../../../utils/oneOf';
import { stringOperators } from '../../../../../../constants/operators';
@ -18,14 +19,20 @@ const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
interface ConstraintSingleValueProps {
constraint: IConstraint;
allowExpand: (shouldExpand: boolean) => void;
}
export const ContraintAccordionViewHeaderSingleValue = ({
export const ConstraintAccordionViewHeaderSingleValue = ({
constraint,
allowExpand,
}: ConstraintSingleValueProps) => {
const { locationSettings } = useLocationSettings();
const { classes: styles } = useStyles();
useEffect(() => {
allowExpand(false);
}, [allowExpand]);
return (
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender

View File

@ -1,6 +1,6 @@
import { forwardRef, ReactNode } from 'react';
import { styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { FC, forwardRef } from 'react';
export const StyledIconWrapperBase = styled('div')<{
prefix?: boolean;
@ -24,7 +24,7 @@ const StyledPrefixIconWrapper = styled(StyledIconWrapperBase)(() => ({
export const StyledIconWrapper = forwardRef<
HTMLDivElement,
{ isPrefix?: boolean; children?: React.ReactNode }
{ isPrefix?: boolean; children?: ReactNode }
>(({ isPrefix, ...props }, ref) => (
<ConditionallyRender
condition={Boolean(isPrefix)}

View File

@ -34,4 +34,10 @@ export const useStyles = makeStyles()(theme => ({
borderTopColor: theme.palette.grey[300],
},
},
formInput: {
[theme.breakpoints.between(1101, 1365)]: {
width: '170px',
marginRight: '8px',
},
},
}));

View File

@ -48,7 +48,12 @@ export const ConstraintOperatorSelect = ({
};
return (
<FormControl variant="outlined" size="small" fullWidth>
<FormControl
variant="outlined"
size="small"
fullWidth
className={styles.formInput}
>
<InputLabel htmlFor="operator-select">Operator</InputLabel>
<Select
id="operator-select"

View File

@ -1,13 +0,0 @@
export function getTextWidth(text: string | null) {
if (text != null) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (context != null) {
context.font = getComputedStyle(document.body).font;
return context.measureText(text).width;
}
}
return 0;
}

View File

@ -27,6 +27,7 @@ export const useStyles = makeStyles()(theme => ({
width: formTemplateSidebarWidth,
[theme.breakpoints.down(1100)]: {
width: '100%',
color: 'red',
},
[theme.breakpoints.down(500)]: {
padding: '2rem 1rem',

View File

@ -7,7 +7,7 @@ export const useStyles = makeStyles()(() => ({
right: 0,
bottom: 0,
height: '100vh',
maxWidth: '90vw',
maxWidth: '98vw',
width: 1300,
overflow: 'auto',
boxShadow: '0 0 1rem rgba(0, 0, 0, 0.25)',

View File

@ -6,4 +6,7 @@ export const useStyles = makeStyles()(theme => ({
fontSize: theme.fontSizes.bodySize,
fontWeight: theme.fontWeight.bold,
},
divider: {
border: `1px dashed ${theme.palette.divider}`,
},
}));

View File

@ -9,6 +9,7 @@ import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/Fe
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs/SegmentDocs';
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
import { Divider } from '@mui/material';
interface IFeatureStrategySegmentProps {
segments: ISegment[];
@ -65,6 +66,7 @@ export const FeatureStrategySegment = ({
segments={selectedSegments}
setSegments={setSelectedSegments}
/>
<Divider className={styles.divider} />
</>
);
};

View File

@ -22,4 +22,7 @@ export const useStyles = makeStyles()(theme => ({
alignItems: 'center',
borderRadius: theme.shape.borderRadius,
},
selectedSegmentsLabel: {
color: theme.palette.text.secondary,
},
}));

View File

@ -24,6 +24,14 @@ export const FeatureStrategySegmentList = ({
return (
<>
<ConditionallyRender
condition={segments && segments.length > 0}
show={
<p className={styles.selectedSegmentsLabel}>
Selected Segments
</p>
}
/>
<div className={styles.list}>
{segments.map((segment, i) => (
<Fragment key={segment.id}>

View File

@ -23,8 +23,6 @@ import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
import { formatUnknownError } from 'utils/formatUnknownError';
import { IUser } from 'interfaces/user';
import { IGroup } from 'interfaces/group';
import { useUsers } from 'hooks/api/getters/useUsers/useUsers';
import { useGroups } from 'hooks/api/getters/useGroups/useGroups';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ProjectRoleDescription } from './ProjectRoleDescription/ProjectRoleDescription';
import { useAccess } from '../../../../hooks/api/getters/useAccess/useAccess';