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

feat: improve variants modal UI/UX (#3307)

https://linear.app/unleash/issue/2-758/add-variant-improve-the-flow


![image](https://user-images.githubusercontent.com/14320932/225064841-7fdb3b23-a06d-4078-b33a-50166e54a8b8.png)

![image](https://user-images.githubusercontent.com/14320932/225063913-ff92a563-7aa8-493f-a0dd-ef16f1474151.png)

### Variants form

- Fix variants edit form to follow natural tab order;
- Update variants form UI to new design with multiple improvements and
fixes, including a sticky header;
- New variants are now added at the bottom of the edit form instead of
at the top, with a smooth scroll and focus;

### Change requests

- On the variants diff, use variant names instead of index;
- Use an object-based diff logic (instead of array-based) for cleaner
diffs on variants (thanks @thomasheartman !);
- Display a table with the new variants data and display the diff on a
`TooltipLink`;
- Adapt strategy CR changes to the new `TooltipLink` logic for
consistency;

### Other

- `TooltipLink` and `Badge` components are now tab-selectable;
- Small enhancements, refactors and improvements;

---------

Co-authored-by: Gastón Fournier <gaston@getunleash.io>
This commit is contained in:
Nuno Góis 2023-03-15 12:22:06 +00:00 committed by GitHub
parent 6aeadfff33
commit 4e36981c96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 337 additions and 247 deletions

View File

@ -10,9 +10,9 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { Alert, Box, styled } from '@mui/material';
import {
CodeSnippetPopover,
PopoverDiff,
} from '../../CodeSnippetPopover/CodeSnippetPopover';
StrategyTooltipLink,
StrategyDiff,
} from 'component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink';
import { StrategyExecution } from '../../../../feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
import { ToggleStatusChange } from './ToggleStatusChange';
import {
@ -98,14 +98,14 @@ export const Change: FC<{
{change.action === 'addStrategy' && (
<>
<StrategyAddedChange discard={discard}>
<CodeSnippetPopover change={change}>
<PopoverDiff
<StrategyTooltipLink change={change}>
<StrategyDiff
change={change}
feature={feature.name}
environmentName={changeRequest.environment}
project={changeRequest.project}
/>
</CodeSnippetPopover>
</StrategyTooltipLink>
</StrategyAddedChange>
<StrategyExecution strategy={change.payload} />
</>
@ -113,28 +113,28 @@ export const Change: FC<{
{change.action === 'deleteStrategy' && (
<StrategyDeletedChange discard={discard}>
{hasNameField(change.payload) && (
<CodeSnippetPopover change={change}>
<PopoverDiff
<StrategyTooltipLink change={change}>
<StrategyDiff
change={change}
feature={feature.name}
environmentName={changeRequest.environment}
project={changeRequest.project}
/>
</CodeSnippetPopover>
</StrategyTooltipLink>
)}
</StrategyDeletedChange>
)}
{change.action === 'updateStrategy' && (
<>
<StrategyEditedChange discard={discard}>
<CodeSnippetPopover change={change}>
<PopoverDiff
<StrategyTooltipLink change={change}>
<StrategyDiff
change={change}
feature={feature.name}
environmentName={changeRequest.environment}
project={changeRequest.project}
/>
</CodeSnippetPopover>
</StrategyTooltipLink>
</StrategyEditedChange>
<StrategyExecution strategy={change.payload} />
</>

View File

@ -1,5 +1,6 @@
import { styled } from '@mui/material';
import EventDiff from 'component/events/EventDiff/EventDiff';
import { IFeatureVariant } from 'interfaces/featureToggle';
const StyledCodeSection = styled('div')(({ theme }) => ({
overflowX: 'auto',
@ -13,17 +14,24 @@ const StyledCodeSection = styled('div')(({ theme }) => ({
}));
interface IDiffProps {
preData: any;
data: any;
preData: IFeatureVariant[];
data: IFeatureVariant[];
}
const variantsArrayToObject = (variants: IFeatureVariant[]) =>
variants.reduce(
(object, { name, ...variant }) => ({ ...object, [name]: variant }),
{}
);
export const Diff = ({ preData, data }: IDiffProps) => (
<StyledCodeSection>
<EventDiff
entry={{
preData,
data,
preData: variantsArrayToObject(preData),
data: variantsArrayToObject(data),
}}
sort={(a, b) => a.index - b.index}
/>
</StyledCodeSection>
);

View File

@ -1,20 +1,33 @@
import { Box, styled, Typography } from '@mui/material';
import { Box, styled } from '@mui/material';
import { IChangeRequestPatchVariant } from 'component/changeRequest/changeRequest.types';
import { Badge } from 'component/common/Badge/Badge';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
import { EnvironmentVariantsTable } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { ReactNode } from 'react';
import { Diff } from './Diff';
export const ChangeItemCreateEditWrapper = styled(Box)(({ theme }) => ({
const ChangeItemInfo = styled(Box)({
display: 'flex',
flexDirection: 'column',
});
const StyledChangeHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: theme.spacing(2),
lineHeight: theme.spacing(3),
}));
const ChangeItemInfo = styled(Box)(({ theme }) => ({
const StyledStickinessContainer = styled('div')(({ theme }) => ({
marginTop: theme.spacing(1.5),
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
alignItems: 'center',
gap: theme.spacing(1.5),
marginBottom: theme.spacing(0.5),
fontSize: theme.fontSizes.smallBody,
}));
interface IVariantPatchProps {
@ -34,17 +47,44 @@ export const VariantPatch = ({
}: IVariantPatchProps) => {
const { feature: featureData } = useFeature(project, feature);
const preData = featureData.environments.find(
({ name }) => environment === name
)?.variants;
const preData =
featureData.environments.find(({ name }) => environment === name)
?.variants ?? [];
return (
<ChangeItemCreateEditWrapper>
<ChangeItemInfo>
<Typography>Updating variants:</Typography>
<Diff preData={preData} data={change.payload.variants} />
</ChangeItemInfo>
{discard}
</ChangeItemCreateEditWrapper>
<ChangeItemInfo>
<StyledChangeHeader>
<TooltipLink
tooltip={
<Diff
preData={preData}
data={change.payload.variants}
/>
}
tooltipProps={{
maxWidth: 500,
maxHeight: 600,
}}
>
Updating variants to:
</TooltipLink>
{discard}
</StyledChangeHeader>
<EnvironmentVariantsTable variants={change.payload.variants} />
<ConditionallyRender
condition={change.payload.variants.length > 1}
show={
<>
<StyledStickinessContainer>
<p>Stickiness:</p>
<Badge>
{change.payload.variants[0]?.stickiness ||
'default'}
</Badge>
</StyledStickinessContainer>
</>
}
/>
</ChangeItemInfo>
);
};

View File

@ -1,122 +0,0 @@
import {
IChangeRequestAddStrategy,
IChangeRequestDeleteStrategy,
IChangeRequestUpdateStrategy,
} from '../../changeRequest.types';
import React, { FC } from 'react';
import {
formatStrategyName,
GetFeatureStrategyIcon,
} from '../../../../utils/strategyNames';
import { Popover, Typography } from '@mui/material';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { StyledCodeSection } from '../../../events/EventCard/EventCard';
import EventDiff from '../../../events/EventDiff/EventDiff';
import omit from 'lodash.omit';
const useCurrentStrategy = (
change:
| IChangeRequestAddStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestDeleteStrategy,
project: string,
feature: string,
environmentName: string
) => {
const currentFeature = useFeature(project, feature);
const currentStrategy = currentFeature.feature?.environments
.find(environment => environment.name === environmentName)
?.strategies.find(
strategy =>
'id' in change.payload && strategy.id === change.payload.id
);
return currentStrategy;
};
export const PopoverDiff: FC<{
change:
| IChangeRequestAddStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestDeleteStrategy;
project: string;
feature: string;
environmentName: string;
}> = ({ change, project, feature, environmentName }) => {
const currentStrategy = useCurrentStrategy(
change,
project,
feature,
environmentName
);
const changeRequestStrategy =
change.action === 'deleteStrategy' ? undefined : change.payload;
return (
<StyledCodeSection>
<EventDiff
entry={{
preData: omit(currentStrategy, 'sortOrder'),
data: changeRequestStrategy,
}}
/>
</StyledCodeSection>
);
};
interface ICodeSnippetPopoverProps {
change:
| IChangeRequestAddStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestDeleteStrategy;
}
// based on: https://mui.com/material-ui/react-popover/#mouse-over-interaction
export const CodeSnippetPopover: FC<ICodeSnippetPopoverProps> = ({
change,
children,
}) => {
const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);
const handlePopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
return (
<>
<GetFeatureStrategyIcon strategyName={change.payload.name} />
<Typography
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
>
{formatStrategyName(change.payload.name)}
</Typography>
<Popover
id={String(change.id)}
sx={{
pointerEvents: 'none',
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
{children}
</Popover>
</>
);
};

View File

@ -0,0 +1,100 @@
import {
IChangeRequestAddStrategy,
IChangeRequestDeleteStrategy,
IChangeRequestUpdateStrategy,
} from 'component/changeRequest/changeRequest.types';
import { FC } from 'react';
import {
formatStrategyName,
GetFeatureStrategyIcon,
} from 'utils/strategyNames';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import EventDiff from 'component/events/EventDiff/EventDiff';
import omit from 'lodash.omit';
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
import { styled } from '@mui/material';
const StyledCodeSection = styled('div')(({ theme }) => ({
overflowX: 'auto',
'& code': {
wordWrap: 'break-word',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
lineHeight: 1.5,
fontSize: theme.fontSizes.smallBody,
},
}));
const useCurrentStrategy = (
change:
| IChangeRequestAddStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestDeleteStrategy,
project: string,
feature: string,
environmentName: string
) => {
const currentFeature = useFeature(project, feature);
const currentStrategy = currentFeature.feature?.environments
.find(environment => environment.name === environmentName)
?.strategies.find(
strategy =>
'id' in change.payload && strategy.id === change.payload.id
);
return currentStrategy;
};
export const StrategyDiff: FC<{
change:
| IChangeRequestAddStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestDeleteStrategy;
project: string;
feature: string;
environmentName: string;
}> = ({ change, project, feature, environmentName }) => {
const currentStrategy = useCurrentStrategy(
change,
project,
feature,
environmentName
);
const changeRequestStrategy =
change.action === 'deleteStrategy' ? undefined : change.payload;
return (
<StyledCodeSection>
<EventDiff
entry={{
preData: omit(currentStrategy, 'sortOrder'),
data: changeRequestStrategy,
}}
/>
</StyledCodeSection>
);
};
interface IStrategyTooltipLinkProps {
change:
| IChangeRequestAddStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestDeleteStrategy;
}
export const StrategyTooltipLink: FC<IStrategyTooltipLinkProps> = ({
change,
children,
}) => (
<>
<GetFeatureStrategyIcon strategyName={change.payload.name} />
<TooltipLink
tooltip={children}
tooltipProps={{
maxWidth: 500,
maxHeight: 600,
}}
>
{formatStrategyName(change.payload.name)}
</TooltipLink>
</>
);

View File

@ -80,6 +80,7 @@ export const Badge: FC<IBadgeProps> = forwardRef(
ref: ForwardedRef<HTMLDivElement>
) => (
<StyledBadge
tabIndex={0}
color={color}
icon={icon}
className={className}

View File

@ -36,7 +36,6 @@ const StyledContainer = styled('section', {
width: '100%',
display: 'flex',
margin: '0 auto',
overflow: 'hidden',
[theme.breakpoints.down(1100)]: {
flexDirection: 'column',
minHeight: 0,

View File

@ -27,7 +27,7 @@ export const TooltipLink = ({
...props
}: ITooltipLinkProps) => (
<HtmlTooltip title={tooltip} {...tooltipProps} arrow>
<StyledLink highlighted={highlighted} {...props}>
<StyledLink tabIndex={0} highlighted={highlighted} {...props}>
{children}
</StyledLink>
</HtmlTooltip>

View File

@ -10,11 +10,21 @@ const DIFF_PREFIXES: Record<string, string> = {
N: '+',
};
interface IEventDiffProps {
entry: Partial<IEvent>;
interface IEventDiffResult {
key: string;
value: JSX.Element;
index: number;
}
const EventDiff = ({ entry }: IEventDiffProps) => {
interface IEventDiffProps {
entry: Partial<IEvent>;
sort?: (a: IEventDiffResult, b: IEventDiffResult) => number;
}
const EventDiff = ({
entry,
sort = (a, b) => a.key.localeCompare(b.key),
}: IEventDiffProps) => {
const theme = useTheme();
const styles: Record<string, CSSProperties> = {
@ -48,7 +58,7 @@ const EventDiff = ({ entry }: IEventDiffProps) => {
return change;
};
const buildDiff = (diff: any, idx: number) => {
const buildDiff = (diff: any, index: number): IEventDiffResult => {
let change;
const key = diff.path?.join('.') ?? diff.index;
@ -66,15 +76,24 @@ const EventDiff = ({ entry }: IEventDiffProps) => {
</div>
);
} else {
const changeValue = JSON.stringify(diff.rhs || diff.item);
change = (
<div style={styles[diff.kind]}>
{DIFF_PREFIXES[diff.kind]} {key}:{' '}
{JSON.stringify(diff.rhs || diff.item)}
{DIFF_PREFIXES[diff.kind]} {key}
{changeValue
? `: ${changeValue}`
: diff.kind === 'D'
? ' (deleted)'
: ''}
</div>
);
}
return { key: key.toString(), value: <div key={idx}>{change}</div> };
return {
key: key.toString(),
value: <div key={index}>{change}</div>,
index,
};
};
let changes;
@ -82,7 +101,7 @@ const EventDiff = ({ entry }: IEventDiffProps) => {
if (diffs) {
changes = diffs
.map(buildDiff)
.sort((a, b) => a.key.localeCompare(b.key))
.sort(sort)
.map(({ value }) => value);
} else {
// Just show the data if there is no diff yet.

View File

@ -48,6 +48,10 @@ const StyledDescription = styled('p')(({ theme }) => ({
marginBottom: theme.spacing(1.5),
}));
const StyledTableContainer = styled('div')(({ theme }) => ({
margin: theme.spacing(3, 0),
}));
const StyledStickinessContainer = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
@ -84,10 +88,12 @@ export const EnvironmentVariantsCard = ({
condition={variants.length > 0}
show={
<>
<EnvironmentVariantsTable
environment={environment}
searchValue={searchValue}
/>
<StyledTableContainer>
<EnvironmentVariantsTable
variants={variants}
searchValue={searchValue}
/>
</StyledTableContainer>
<ConditionallyRender
condition={variants.length > 1}
show={

View File

@ -1,10 +1,4 @@
import {
styled,
TableBody,
TableRow,
useMediaQuery,
useTheme,
} from '@mui/material';
import { TableBody, TableRow, useMediaQuery, useTheme } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import {
SortableTableHeader,
@ -18,11 +12,7 @@ import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightC
import { calculateVariantWeight } from 'component/common/util';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useSearch } from 'hooks/useSearch';
import {
IFeatureEnvironment,
IOverride,
IPayload,
} from 'interfaces/featureToggle';
import { IFeatureVariant, IOverride, IPayload } from 'interfaces/featureToggle';
import { useMemo } from 'react';
import { useSortBy, useTable } from 'react-table';
import { sortTypes } from 'utils/sortTypes';
@ -30,18 +20,14 @@ import { PayloadCell } from './PayloadCell/PayloadCell';
import { OverridesCell } from './OverridesCell/OverridesCell';
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
const StyledTableContainer = styled('div')(({ theme }) => ({
margin: theme.spacing(3, 0),
}));
interface IEnvironmentVariantsTableProps {
environment: IFeatureEnvironment;
searchValue: string;
variants: IFeatureVariant[];
searchValue?: string;
}
export const EnvironmentVariantsTable = ({
environment,
searchValue,
variants,
searchValue = '',
}: IEnvironmentVariantsTableProps) => {
const projectId = useRequiredPathParam('projectId');
@ -49,8 +35,6 @@ export const EnvironmentVariantsTable = ({
const isMediumScreen = useMediaQuery(theme.breakpoints.down('md'));
const isLargeScreen = useMediaQuery(theme.breakpoints.down('lg'));
const variants = environment.variants ?? [];
const columns = useMemo(
() => [
{
@ -107,7 +91,7 @@ export const EnvironmentVariantsTable = ({
sortType: 'alphanumeric',
},
],
[projectId, variants, environment]
[projectId, variants]
);
const initialState = useMemo(
@ -156,7 +140,7 @@ export const EnvironmentVariantsTable = ({
);
return (
<StyledTableContainer>
<>
<SearchHighlightProvider value={getSearchText(searchValue)}>
<Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups as any} />
@ -191,6 +175,6 @@ export const EnvironmentVariantsTable = ({
/>
}
/>
</StyledTableContainer>
</>
);
};

View File

@ -1,4 +1,4 @@
import { Alert, Button, styled } from '@mui/material';
import { Alert, Button, Divider, styled } from '@mui/material';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
@ -29,8 +29,14 @@ const StyledFormSubtitle = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
},
marginTop: theme.spacing(-1.5),
marginBottom: theme.spacing(4),
marginTop: theme.spacing(-3.5),
marginBottom: theme.spacing(2),
backgroundColor: theme.palette.background.default,
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
position: 'sticky',
top: 0,
zIndex: 2,
}));
const StyledCloudCircle = styled(CloudCircle, {
@ -68,7 +74,7 @@ const StyledAlert = styled(Alert)(({ theme }) => ({
const StyledVariantForms = styled('div')({
display: 'flex',
flexDirection: 'column-reverse',
flexDirection: 'column',
});
const StyledStickinessContainer = styled('div')(({ theme }) => ({
@ -84,6 +90,10 @@ const StyledDescription = styled('p')(({ theme }) => ({
marginBottom: theme.spacing(1.5),
}));
const StyledDivider = styled(Divider)(({ theme }) => ({
margin: theme.spacing(4, 0),
}));
const StyledStickinessSelect = styled(StickinessSelect)(({ theme }) => ({
minWidth: theme.spacing(20),
width: '100%',
@ -144,6 +154,7 @@ export const EnvironmentVariantsModal = ({
const oldVariants = environment?.variants || [];
const [variantsEdit, setVariantsEdit] = useState<IFeatureVariantEdit[]>([]);
const [newVariant, setNewVariant] = useState<string>();
useEffect(() => {
setVariantsEdit(
@ -183,6 +194,38 @@ export const EnvironmentVariantsModal = ({
);
};
const addVariant = () => {
const id = uuidv4();
setVariantsEdit(variantsEdit => [
...variantsEdit,
{
name: '',
weightType: WeightType.VARIABLE,
weight: 0,
overrides: [],
stickiness:
variantsEdit?.length > 0
? variantsEdit[0].stickiness
: 'default',
new: true,
isValid: false,
id,
},
]);
setNewVariant(id);
};
useEffect(() => {
if (newVariant) {
const element = document.getElementById(
`variant-name-input-${newVariant}`
);
element?.scrollIntoView({ behavior: 'smooth', block: 'center' });
element?.focus({ preventScroll: true });
setNewVariant(undefined);
}
}, [newVariant]);
const variants = variantsEdit.map(
({ new: _, isValid: __, id: ___, ...rest }) => rest
);
@ -286,24 +329,7 @@ export const EnvironmentVariantsModal = ({
</div>
<PermissionButton
data-testid="MODAL_ADD_VARIANT_BUTTON"
onClick={() =>
setVariantsEdit(variantsEdit => [
...variantsEdit,
{
name: '',
weightType: WeightType.VARIABLE,
weight: 0,
overrides: [],
stickiness:
variantsEdit?.length > 0
? variantsEdit[0].stickiness
: 'default',
new: true,
isValid: false,
id: uuidv4(),
},
])
}
onClick={addVariant}
variant="outlined"
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
projectId={projectId}
@ -359,6 +385,16 @@ export const EnvironmentVariantsModal = ({
/>
))}
</StyledVariantForms>
<PermissionButton
onClick={addVariant}
variant="outlined"
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
projectId={projectId}
environmentId={environment?.name}
>
Add variant
</PermissionButton>
<StyledDivider />
<ConditionallyRender
condition={variantsEdit.length > 0}
show={

View File

@ -65,6 +65,15 @@ const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({
},
}));
const StyledFieldColumn = styled('div')(({ theme }) => ({
width: '100%',
gap: theme.spacing(1.5),
display: 'flex',
'& > div': {
width: '100%',
},
}));
const StyledInput = styled(Input)(() => ({
width: '100%',
}));
@ -103,13 +112,15 @@ const StyledTopRow = styled(StyledRow)({
});
const StyledSelectMenu = styled(SelectMenu)(({ theme }) => ({
minWidth: theme.spacing(20),
marginRight: theme.spacing(10),
[theme.breakpoints.up('sm')]: {
minWidth: theme.spacing(20),
},
}));
const StyledAddOverrideButton = styled(Button)(({ theme }) => ({
width: theme.spacing(20),
maxWidth: '100%',
marginTop: theme.spacing(-1),
marginLeft: theme.spacing(-1),
}));
const payloadOptions = [
@ -319,8 +330,8 @@ export const VariantForm = ({
This will be used to identify the variant in your code
</StyledSubLabel>
<StyledInput
id={`variant-name-input-${variant.id}`}
data-testid="VARIANT_NAME_INPUT"
autoFocus
label="Variant name"
error={Boolean(errors.name)}
errorText={errors.name}
@ -391,27 +402,31 @@ export const VariantForm = ({
}));
}}
/>
<StyledInput
id="variant-payload-value"
name="variant-payload-value"
label="Value"
multiline={payload.type !== 'string'}
rows={payload.type === 'string' ? 1 : 4}
value={payload.value}
onChange={e => {
clearError(ErrorField.PAYLOAD);
setPayload(payload => ({
...payload,
value: e.target.value,
}));
}}
placeholder={
payload.type === 'json' ? '{ "hello": "world" }' : ''
}
onBlur={() => validatePayload(payload)}
error={Boolean(errors.payload)}
errorText={errors.payload}
/>
<StyledFieldColumn>
<StyledInput
id="variant-payload-value"
name="variant-payload-value"
label="Value"
multiline={payload.type !== 'string'}
rows={payload.type === 'string' ? 1 : 4}
value={payload.value}
onChange={e => {
clearError(ErrorField.PAYLOAD);
setPayload(payload => ({
...payload,
value: e.target.value,
}));
}}
placeholder={
payload.type === 'json'
? '{ "hello": "world" }'
: ''
}
onBlur={() => validatePayload(payload)}
error={Boolean(errors.payload)}
errorText={errors.payload}
/>
</StyledFieldColumn>
</StyledRow>
<StyledMarginLabel>
Overrides
@ -421,13 +436,15 @@ export const VariantForm = ({
overrides={overrides}
overridesDispatch={overridesDispatch}
/>
<StyledAddOverrideButton
onClick={onAddOverride}
variant="outlined"
color="primary"
>
Add override
</StyledAddOverrideButton>
<div>
<StyledAddOverrideButton
onClick={onAddOverride}
variant="text"
color="primary"
>
Add override
</StyledAddOverrideButton>
</div>
</StyledVariantForm>
);
};

View File

@ -24,8 +24,10 @@ const StyledRow = styled('div')(({ theme }) => ({
}));
const StyledSelectMenu = styled(SelectMenu)(({ theme }) => ({
minWidth: theme.spacing(20),
marginRight: theme.spacing(10),
[theme.breakpoints.up('sm')]: {
minWidth: theme.spacing(20),
},
}));
const StyledFieldColumn = styled('div')(({ theme }) => ({