From 4e36981c963b9c0a55b75dee14f5dc59da98c3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Wed, 15 Mar 2023 12:22:06 +0000 Subject: [PATCH] feat: improve variants modal UI/UX (#3307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../ChangeRequest/Changes/Change/Change.tsx | 24 ++-- .../Changes/Change/VariantPatch/Diff.tsx | 16 ++- .../Change/VariantPatch/VariantPatch.tsx | 70 +++++++--- .../CodeSnippetPopover/CodeSnippetPopover.tsx | 122 ------------------ .../StrategyTooltipLink.tsx | 100 ++++++++++++++ frontend/src/component/common/Badge/Badge.tsx | 1 + .../common/FormTemplate/FormTemplate.tsx | 1 - .../common/TooltipLink/TooltipLink.tsx | 2 +- .../component/events/EventDiff/EventDiff.tsx | 35 +++-- .../EnvironmentVariantsCard.tsx | 14 +- .../EnvironmentVariantsTable.tsx | 34 ++--- .../EnvironmentVariantsModal.tsx | 80 ++++++++---- .../VariantForm/VariantForm.tsx | 81 +++++++----- .../VariantOverrides/VariantOverrides.tsx | 4 +- 14 files changed, 337 insertions(+), 247 deletions(-) delete mode 100644 frontend/src/component/changeRequest/ChangeRequest/CodeSnippetPopover/CodeSnippetPopover.tsx create mode 100644 frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/Change.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/Change.tsx index cae9773d62..6e9c7e979f 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/Change.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/Change.tsx @@ -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' && ( <> - - + - + @@ -113,28 +113,28 @@ export const Change: FC<{ {change.action === 'deleteStrategy' && ( {hasNameField(change.payload) && ( - - + - + )} )} {change.action === 'updateStrategy' && ( <> - - + - + diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/Diff.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/Diff.tsx index d0caf4aaff..8feb877909 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/Diff.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/Diff.tsx @@ -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) => ( a.index - b.index} /> ); diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/VariantPatch.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/VariantPatch.tsx index d9b1d0e335..f652aa5db5 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/VariantPatch.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/VariantPatch/VariantPatch.tsx @@ -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 ( - - - Updating variants: - - - {discard} - + + + + } + tooltipProps={{ + maxWidth: 500, + maxHeight: 600, + }} + > + Updating variants to: + + {discard} + + + 1} + show={ + <> + +

Stickiness:

+ + {change.payload.variants[0]?.stickiness || + 'default'} + +
+ + } + /> +
); }; diff --git a/frontend/src/component/changeRequest/ChangeRequest/CodeSnippetPopover/CodeSnippetPopover.tsx b/frontend/src/component/changeRequest/ChangeRequest/CodeSnippetPopover/CodeSnippetPopover.tsx deleted file mode 100644 index 80894b971e..0000000000 --- a/frontend/src/component/changeRequest/ChangeRequest/CodeSnippetPopover/CodeSnippetPopover.tsx +++ /dev/null @@ -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 ( - - - - ); -}; -interface ICodeSnippetPopoverProps { - change: - | IChangeRequestAddStrategy - | IChangeRequestUpdateStrategy - | IChangeRequestDeleteStrategy; -} - -// based on: https://mui.com/material-ui/react-popover/#mouse-over-interaction -export const CodeSnippetPopover: FC = ({ - change, - children, -}) => { - const [anchorEl, setAnchorEl] = React.useState(null); - - const handlePopoverOpen = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handlePopoverClose = () => { - setAnchorEl(null); - }; - - const open = Boolean(anchorEl); - - return ( - <> - - - - {formatStrategyName(change.payload.name)} - - - {children} - - - ); -}; diff --git a/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx b/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx new file mode 100644 index 0000000000..abd73211f9 --- /dev/null +++ b/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx @@ -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 ( + + + + ); +}; +interface IStrategyTooltipLinkProps { + change: + | IChangeRequestAddStrategy + | IChangeRequestUpdateStrategy + | IChangeRequestDeleteStrategy; +} + +export const StrategyTooltipLink: FC = ({ + change, + children, +}) => ( + <> + + + {formatStrategyName(change.payload.name)} + + +); diff --git a/frontend/src/component/common/Badge/Badge.tsx b/frontend/src/component/common/Badge/Badge.tsx index 34556cdd07..5a850f7e1d 100644 --- a/frontend/src/component/common/Badge/Badge.tsx +++ b/frontend/src/component/common/Badge/Badge.tsx @@ -80,6 +80,7 @@ export const Badge: FC = forwardRef( ref: ForwardedRef ) => ( ( - + {children} diff --git a/frontend/src/component/events/EventDiff/EventDiff.tsx b/frontend/src/component/events/EventDiff/EventDiff.tsx index e87db6ae29..ac9424dcf0 100644 --- a/frontend/src/component/events/EventDiff/EventDiff.tsx +++ b/frontend/src/component/events/EventDiff/EventDiff.tsx @@ -10,11 +10,21 @@ const DIFF_PREFIXES: Record = { N: '+', }; -interface IEventDiffProps { - entry: Partial; +interface IEventDiffResult { + key: string; + value: JSX.Element; + index: number; } -const EventDiff = ({ entry }: IEventDiffProps) => { +interface IEventDiffProps { + entry: Partial; + sort?: (a: IEventDiffResult, b: IEventDiffResult) => number; +} + +const EventDiff = ({ + entry, + sort = (a, b) => a.key.localeCompare(b.key), +}: IEventDiffProps) => { const theme = useTheme(); const styles: Record = { @@ -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) => { ); } else { + const changeValue = JSON.stringify(diff.rhs || diff.item); change = (
- {DIFF_PREFIXES[diff.kind]} {key}:{' '} - {JSON.stringify(diff.rhs || diff.item)} + {DIFF_PREFIXES[diff.kind]} {key} + {changeValue + ? `: ${changeValue}` + : diff.kind === 'D' + ? ' (deleted)' + : ''}
); } - return { key: key.toString(), value:
{change}
}; + return { + key: key.toString(), + value:
{change}
, + 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. diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsCard.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsCard.tsx index 5a48548655..dbbbaa2c92 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsCard.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsCard.tsx @@ -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={ <> - + + + 1} show={ diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable.tsx index f288f70977..5faf9dc4f0 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable.tsx @@ -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 ( - + <> @@ -191,6 +175,6 @@ export const EnvironmentVariantsTable = ({ /> } /> - + ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/EnvironmentVariantsModal.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/EnvironmentVariantsModal.tsx index 465a7d9386..7bbaaed87c 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/EnvironmentVariantsModal.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/EnvironmentVariantsModal.tsx @@ -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([]); + const [newVariant, setNewVariant] = useState(); 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 = ({ - 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 = ({ /> ))} + + Add variant + + 0} show={ diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantForm.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantForm.tsx index 0ab51b5107..8fe29b5519 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantForm.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantForm.tsx @@ -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 - { - 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} - /> + + { + 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} + /> + Overrides @@ -421,13 +436,15 @@ export const VariantForm = ({ overrides={overrides} overridesDispatch={overridesDispatch} /> - - Add override - +
+ + Add override + +
); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides.tsx index 34dab01973..c4cc63e053 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides.tsx @@ -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 }) => ({