Skip to content

Commit

Permalink
Conditional auto cols/rows input in the inspector, auto template in t…
Browse files Browse the repository at this point in the history
…he advanced modal (#6541)

This is a followup to
#6533

This PR improves the previous iteration by:

1. showing the auto template input in the inspector only if there are no
template dimensions, or the auto template is set explicitly to something
that's not `auto`
2. showing the auto templates for both cols and rows in the advanced
modal
3. support dimming `auto` values in grid expression inputs to `fg6`


https://github.com/user-attachments/assets/e3ecb7f5-36ed-4a96-80d5-d3d4278e33b1




Fixes #6540
  • Loading branch information
ruggi authored Oct 16, 2024
1 parent 03f7675 commit e6f3148
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 116 deletions.
32 changes: 32 additions & 0 deletions editor/src/components/inspector/common/css-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import { parseFlex, printFlexAsAttributeValue } from '../../../printer-parsers/c
import { memoize } from '../../../core/shared/memoize'
import * as csstree from 'css-tree'
import type { IcnProps } from '../../../uuiui'
import { cssNumberEqual } from '../../canvas/controls/select-mode/controls-common'

var combineRegExp = function (regexpList: Array<RegExp | string>, flags?: string) {
let source: string = ''
Expand Down Expand Up @@ -595,6 +596,37 @@ export type GridCSSKeyword = BaseGridDimension & {
value: CSSKeyword<ValidGridDimensionKeyword>
}

export function gridDimensionsAreEqual(a: GridDimension, b: GridDimension): boolean {
switch (a.type) {
case 'KEYWORD':
if (a.type !== b.type) {
return false
}
return a.value.type === b.value.type && a.value.value === b.value.value
case 'NUMBER':
if (a.type !== b.type) {
return false
}
return cssNumberEqual(a.value, b.value)
case 'MINMAX':
if (a.type !== b.type) {
return false
}
return gridDimensionsAreEqual(a.min, b.min) && gridDimensionsAreEqual(a.max, b.max)
case 'REPEAT':
if (a.type !== b.type) {
return false
}
return (
a.times === b.times &&
a.value.length === b.value.length &&
a.value.every((value, index) => gridDimensionsAreEqual(value, b.value[index]))
)
default:
assertNever(a)
}
}

type BaseGridCSSRepeat = {
type: 'REPEAT'
value: Array<GridDimension>
Expand Down
40 changes: 40 additions & 0 deletions editor/src/components/inspector/controls/advanced-grid-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import {
import { optionalMap } from '../../../core/shared/optional-utils'
import type { FlexAlignment } from 'utopia-api/core'
import { FlexJustifyContent } from 'utopia-api/core'
import { GridAutoColsOrRowsControlInner } from '../grid-auto-cols-or-rows-control'
import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook'
import { MetadataUtils } from '../../../core/model/element-metadata-utils'
import { selectedViewsSelector } from '../inpector-selectors'

export interface AdvancedGridModalProps {
id: string
Expand Down Expand Up @@ -103,6 +107,25 @@ export const AdvancedGridModal = React.memo((props: AdvancedGridModalProps) => {
[alignContentLayoutInfo],
)

const selectedViewsRef = useRefEditorState(selectedViewsSelector)
const grid = useEditorState(
Substores.metadata,
(store) => {
if (selectedViewsRef.current.length !== 1) {
return null
}
return MetadataUtils.findElementByElementPath(
store.editor.jsxMetadata,
selectedViewsRef.current[0],
)
},
'AdvancedGridModal grid',
)

if (grid == null) {
return null
}

const advancedGridModal = (
<InspectorModal
offsetX={modalOffset.x}
Expand Down Expand Up @@ -191,6 +214,23 @@ export const AdvancedGridModal = React.memo((props: AdvancedGridModalProps) => {
onOpenChange={toggleAlignContentDropdown}
/>
</UIGridRow>
<UIGridRow padded variant='<-------------1fr------------->'>
<span style={{ fontWeight: 600 }}>Template</span>
</UIGridRow>
<UIGridRow
padded
variant={rowVariant}
className={`ignore-react-onclickoutside-${props.id}`}
>
<GridAutoColsOrRowsControlInner grid={grid} axis='column' label='Auto Cols' />
</UIGridRow>
<UIGridRow
padded
variant={rowVariant}
className={`ignore-react-onclickoutside-${props.id}`}
>
<GridAutoColsOrRowsControlInner grid={grid} axis='row' label='Auto Rows' />
</UIGridRow>
</FlexColumn>
</InspectorModal>
)
Expand Down
17 changes: 13 additions & 4 deletions editor/src/components/inspector/flex-section.spec.browser2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { selectComponentsForTest } from '../../utils/utils.test-utils'
import { renderTestEditorWithCode } from '../canvas/ui-jsx.test-utils'
import * as EP from '../../core/shared/element-path'
import { act, fireEvent, screen } from '@testing-library/react'
import { GridAutoColsOrRowsControlTestId } from './flex-section'
import { GridAutoColsOrRowsControlTestId } from './grid-auto-cols-or-rows-control'

describe('flex section', () => {
describe('grid dimensions', () => {
Expand Down Expand Up @@ -78,7 +78,10 @@ describe('flex section', () => {
})
describe('auto cols/rows', () => {
it('can set a number', async () => {
const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report')
const renderResult = await renderTestEditorWithCode(
gridProjectWithoutTemplate,
'await-first-dom-report',
)
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')])
const control: HTMLInputElement = await screen.findByTestId(
GridAutoColsOrRowsControlTestId('column'),
Expand All @@ -89,7 +92,10 @@ describe('flex section', () => {
expect(control.value).toBe('50px')
})
it('can set a keyword', async () => {
const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report')
const renderResult = await renderTestEditorWithCode(
gridProjectWithoutTemplate,
'await-first-dom-report',
)
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')])
const control: HTMLInputElement = await screen.findByTestId(
GridAutoColsOrRowsControlTestId('column'),
Expand All @@ -100,7 +106,10 @@ describe('flex section', () => {
expect(control.value).toBe('min-content')
})
it('can set an expression', async () => {
const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report')
const renderResult = await renderTestEditorWithCode(
gridProjectWithoutTemplate,
'await-first-dom-report',
)
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')])
const control: HTMLInputElement = await screen.findByTestId(
GridAutoColsOrRowsControlTestId('column'),
Expand Down
147 changes: 35 additions & 112 deletions editor/src/components/inspector/flex-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ import {
gridCSSNumber,
isCSSKeyword,
isCSSNumber,
isEmptyInputValue,
isGridCSSNumber,
printArrayGridDimensions,
type GridDimension,
} from './common/css-utils'
Expand Down Expand Up @@ -91,6 +89,12 @@ import {
} from '../canvas/controls/select-mode/select-mode-hooks'
import type { Axis } from '../canvas/gap-utils'
import { GridExpressionInput } from '../../uuiui/inputs/grid-expression-input'
import {
gridDimensionDropdownKeywords,
parseGridDimensionInput,
useGridExpressionInputFocused,
} from './grid-helpers'
import { GridAutoColsOrRowsControl } from './grid-auto-cols-or-rows-control'

function getLayoutSystem(
layoutSystem: DetectedLayoutSystem | null | undefined,
Expand Down Expand Up @@ -233,6 +237,12 @@ const TemplateDimensionControl = React.memo(
return fromProps
}, [grid, axis, values])

const autoTemplate = React.useMemo(() => {
return axis === 'column'
? grid.specialSizeMeasurements.containerGridPropertiesFromProps.gridAutoColumns
: grid.specialSizeMeasurements.containerGridPropertiesFromProps.gridAutoRows
}, [grid, axis])

const onUpdateDimension = React.useCallback(
(index: number) => (newValue: GridDimension) => {
if (template?.type !== 'DIMENSIONS') {
Expand Down Expand Up @@ -464,6 +474,20 @@ const TemplateDimensionControl = React.memo(

const dimensionsWithGeneratedIndexes = useGeneratedIndexesFromGridDimensions(values)

const showAutoColsOrRows = React.useMemo(() => {
return (
template?.type !== 'DIMENSIONS' ||
template.dimensions.length === 0 ||
(autoTemplate?.type === 'DIMENSIONS' &&
autoTemplate.dimensions.length > 0 &&
!(
autoTemplate.dimensions.length === 1 &&
autoTemplate.dimensions[0].type === 'KEYWORD' &&
autoTemplate.dimensions[0].value.value === 'auto'
))
)
}, [template, autoTemplate])

return (
<div
style={{
Expand Down Expand Up @@ -492,19 +516,20 @@ const TemplateDimensionControl = React.memo(
opener={openDropdown}
/>
))}
<AutoColsOrRowsControl grid={grid} axis={axis} />
{when(
showAutoColsOrRows,
<GridAutoColsOrRowsControl
grid={grid}
axis={axis}
label={axis === 'column' ? 'Auto Cols' : 'Auto Rows'}
/>,
)}
</div>
)
},
)
TemplateDimensionControl.displayName = 'TemplateDimensionControl'

const gridDimensionDropdownKeywords = [
{ label: 'Auto', value: cssKeyword('auto') },
{ label: 'Min-Content', value: cssKeyword('min-content') },
{ label: 'Max-Content', value: cssKeyword('max-content') },
]

function AxisDimensionControl({
value,
index,
Expand Down Expand Up @@ -597,6 +622,7 @@ function AxisDimensionControl({
onFocus={gridExpressionInputFocused.onFocus}
onBlur={gridExpressionInputFocused.onBlur}
keywords={gridDimensionDropdownKeywords}
defaultValue={gridCSSKeyword(cssKeyword('auto'), null)}
/>
{when(
(isHovered && !gridExpressionInputFocused.focused) || isOpen,
Expand Down Expand Up @@ -1090,106 +1116,3 @@ function useGeneratedIndexesFromGridDimensions(
return result
}, [dimensions])
}

const useGridExpressionInputFocused = () => {
const [focused, setFocused] = React.useState(false)
const onFocus = React.useCallback(() => setFocused(true), [])
const onBlur = React.useCallback(() => setFocused(false), [])
return { focused, onFocus, onBlur }
}

function parseGridDimensionInput(
value: UnknownOrEmptyInput<CSSNumber | CSSKeyword<ValidGridDimensionKeyword>>,
currentValue: GridDimension | null,
) {
if (isCSSNumber(value)) {
const maybeUnit =
currentValue != null && isGridCSSNumber(currentValue) ? currentValue.value.unit : null
return gridCSSNumber(
cssNumber(value.value, value.unit ?? maybeUnit),
currentValue?.areaName ?? null,
)
} else if (isCSSKeyword(value)) {
return gridCSSKeyword(value, currentValue?.areaName ?? null)
} else if (isEmptyInputValue(value)) {
return gridCSSKeyword(cssKeyword('auto'), currentValue?.areaName ?? null)
} else {
return null
}
}

const AutoColsOrRowsControl = React.memo(
(props: { axis: 'column' | 'row'; grid: ElementInstanceMetadata }) => {
const value = React.useMemo(() => {
const template = props.grid.specialSizeMeasurements.containerGridPropertiesFromProps
const data = props.axis === 'column' ? template.gridAutoColumns : template.gridAutoRows
if (data?.type !== 'DIMENSIONS') {
return null
}
return data.dimensions[0]
}, [props.grid, props.axis])

const dispatch = useDispatch()

const onUpdateDimension = React.useCallback(
(newDimension: GridDimension) => {
dispatch([
applyCommandsAction([
setProperty(
'always',
props.grid.elementPath,
PP.create('style', props.axis === 'column' ? 'gridAutoColumns' : 'gridAutoRows'),
printArrayGridDimensions([newDimension]),
),
]),
])
},
[props.grid, props.axis, dispatch],
)

const onUpdateNumberOrKeyword = React.useCallback(
(newValue: UnknownOrEmptyInput<CSSNumber | CSSKeyword<ValidGridDimensionKeyword>>) => {
const parsed = parseGridDimensionInput(newValue, null)
if (parsed == null) {
return
}
onUpdateDimension(parsed)
},
[onUpdateDimension],
)

const autoColsOrRowsValueFocused = useGridExpressionInputFocused()

return (
<div
style={{
display: 'grid',
gridAutoFlow: 'column',
alignItems: 'center',
gap: 6,
gridTemplateColumns: autoColsOrRowsValueFocused.focused
? '40px auto'
: `40px auto ${UtopiaTheme.layout.inputHeight.default}px`,
gridTemplateRows: '1fr',
width: '100%',
}}
>
<div>Default</div>
<GridExpressionInput
testId={GridAutoColsOrRowsControlTestId(props.axis)}
value={value ?? gridCSSKeyword(cssKeyword('auto'), null)}
onUpdateNumberOrKeyword={onUpdateNumberOrKeyword}
onUpdateDimension={onUpdateDimension}
onFocus={autoColsOrRowsValueFocused.onFocus}
onBlur={autoColsOrRowsValueFocused.onBlur}
keywords={gridDimensionDropdownKeywords}
/>
</div>
)
},
)
AutoColsOrRowsControl.displayName = 'AutoColsOrRowsControl'

export function GridAutoColsOrRowsControlTestId(axis: 'column' | 'row'): string {
return `grid-template-auto-${axis}`
}
Loading

0 comments on commit e6f3148

Please sign in to comment.