diff --git a/editor/src/components/inspector/common/css-utils.ts b/editor/src/components/inspector/common/css-utils.ts index 0e982adcfddb..a275e06c50ec 100644 --- a/editor/src/components/inspector/common/css-utils.ts +++ b/editor/src/components/inspector/common/css-utils.ts @@ -574,6 +574,10 @@ const CSSNumberUnits: Array = [ '%', ] +export function isFR(unit: CSSNumberUnit): unit is 'fr' { + return unit === 'fr' +} + export interface CSSNumber { value: number unit: CSSNumberUnit | null diff --git a/editor/src/uuiui/inputs/grid-expression-input.tsx b/editor/src/uuiui/inputs/grid-expression-input.tsx index f86e841fd60f..12a8cbaddcb1 100644 --- a/editor/src/uuiui/inputs/grid-expression-input.tsx +++ b/editor/src/uuiui/inputs/grid-expression-input.tsx @@ -3,9 +3,11 @@ import { jsx } from '@emotion/react' import type { CSSProperties } from 'react' import React from 'react' +import type { CSSNumberUnit } from '../../components/inspector/common/css-utils' import { cssKeyword, gridDimensionsAreEqual, + isFR, isGridCSSNumber, isValidGridDimensionKeyword, parseCSSNumber, @@ -28,6 +30,9 @@ import { Icons, SmallerIcons } from '../icons' import { NO_OP } from '../../core/shared/utils' import { unless } from '../../utils/react-conditionals' import { useColorTheme, UtopiaTheme } from '../styles/theme' +import type { Optic } from '../../core/shared/optics/optics' +import { fromField, fromTypeGuard, notNull } from '../../core/shared/optics/optic-creators' +import { exists, modify } from '../../core/shared/optics/optic-utilities' interface GridExpressionInputProps { testId: string @@ -43,6 +48,8 @@ interface GridExpressionInputProps { const DropdownWidth = 25 +const ArrowKeyFractionalIncrement = 0.1 + export const GridExpressionInput = React.memo( ({ testId, @@ -66,32 +73,56 @@ export const GridExpressionInput = React.memo( const onKeyDown = React.useCallback( (e: React.KeyboardEvent) => { - if (e.key !== 'Enter') { - return - } - if (isValidGridDimensionKeyword(printValue)) { - return onUpdateNumberOrKeyword(cssKeyword(printValue)) - } + switch (e.key) { + case 'Enter': + if (isValidGridDimensionKeyword(printValue)) { + return onUpdateNumberOrKeyword(cssKeyword(printValue)) + } - const defaultUnit = isGridCSSNumber(value) ? value.value.unit : 'px' - const maybeNumber = parseCSSNumber(printValue, 'AnyValid', defaultUnit) - if (isRight(maybeNumber)) { - return onUpdateNumberOrKeyword(maybeNumber.value) - } + const defaultUnit = isGridCSSNumber(value) ? value.value.unit : 'px' + const maybeNumber = parseCSSNumber(printValue, 'AnyValid', defaultUnit) + if (isRight(maybeNumber)) { + return onUpdateNumberOrKeyword(maybeNumber.value) + } - const maybeMinmax = parseGridCSSMinmaxOrRepeat(printValue) - if (maybeMinmax != null) { - return onUpdateDimension({ - ...maybeMinmax, - lineName: value.lineName, - } as GridDimension) - } + const maybeMinmax = parseGridCSSMinmaxOrRepeat(printValue) + if (maybeMinmax != null) { + return onUpdateDimension({ + ...maybeMinmax, + lineName: value.lineName, + } as GridDimension) + } - if (printValue === '') { - return onUpdateNumberOrKeyword(cssKeyword('auto')) - } + if (printValue === '') { + return onUpdateNumberOrKeyword(cssKeyword('auto')) + } - setPrintValue(stringifyGridDimension(value)) + setPrintValue(stringifyGridDimension(value)) + break + case 'ArrowUp': + case 'ArrowDown': + e.preventDefault() + const gridNumberValueOptic: Optic = fromTypeGuard( + isGridCSSNumber, + ).compose(fromField('value')) + const valueUnitOptic: Optic = gridNumberValueOptic + .compose(fromField('unit')) + .compose(notNull()) + .compose(fromTypeGuard(isFR)) + const gridNumberNumberOptic: Optic = + gridNumberValueOptic.compose(fromField('value')) + if (exists(valueUnitOptic, value)) { + function updateFractional(fractionalValue: number): number { + return ( + fractionalValue + + (e.key === 'ArrowUp' ? ArrowKeyFractionalIncrement : -ArrowKeyFractionalIncrement) + ) + } + const updatedDimension = modify(gridNumberNumberOptic, updateFractional, value) + onUpdateDimension(updatedDimension) + } + break + } }, [printValue, onUpdateNumberOrKeyword, onUpdateDimension, value], )