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:
parent
c70b38a62a
commit
c3e9b49e12
@ -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',
|
||||
|
@ -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',
|
||||
},
|
||||
}));
|
||||
|
@ -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,
|
||||
},
|
||||
}));
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
@ -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={
|
||||
|
@ -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
|
@ -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)}
|
||||
|
@ -34,4 +34,10 @@ export const useStyles = makeStyles()(theme => ({
|
||||
borderTopColor: theme.palette.grey[300],
|
||||
},
|
||||
},
|
||||
formInput: {
|
||||
[theme.breakpoints.between(1101, 1365)]: {
|
||||
width: '170px',
|
||||
marginRight: '8px',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
@ -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',
|
||||
|
@ -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)',
|
||||
|
@ -6,4 +6,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
},
|
||||
divider: {
|
||||
border: `1px dashed ${theme.palette.divider}`,
|
||||
},
|
||||
}));
|
||||
|
@ -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} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -22,4 +22,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
alignItems: 'center',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
},
|
||||
selectedSegmentsLabel: {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
}));
|
||||
|
@ -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}>
|
||||
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user