Skip to content

Commit e6f3148

Browse files
authored
Conditional auto cols/rows input in the inspector, auto template in the 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
1 parent 03f7675 commit e6f3148

7 files changed

+294
-116
lines changed

editor/src/components/inspector/common/css-utils.ts

+32
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ import { parseFlex, printFlexAsAttributeValue } from '../../../printer-parsers/c
8383
import { memoize } from '../../../core/shared/memoize'
8484
import * as csstree from 'css-tree'
8585
import type { IcnProps } from '../../../uuiui'
86+
import { cssNumberEqual } from '../../canvas/controls/select-mode/controls-common'
8687

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

599+
export function gridDimensionsAreEqual(a: GridDimension, b: GridDimension): boolean {
600+
switch (a.type) {
601+
case 'KEYWORD':
602+
if (a.type !== b.type) {
603+
return false
604+
}
605+
return a.value.type === b.value.type && a.value.value === b.value.value
606+
case 'NUMBER':
607+
if (a.type !== b.type) {
608+
return false
609+
}
610+
return cssNumberEqual(a.value, b.value)
611+
case 'MINMAX':
612+
if (a.type !== b.type) {
613+
return false
614+
}
615+
return gridDimensionsAreEqual(a.min, b.min) && gridDimensionsAreEqual(a.max, b.max)
616+
case 'REPEAT':
617+
if (a.type !== b.type) {
618+
return false
619+
}
620+
return (
621+
a.times === b.times &&
622+
a.value.length === b.value.length &&
623+
a.value.every((value, index) => gridDimensionsAreEqual(value, b.value[index]))
624+
)
625+
default:
626+
assertNever(a)
627+
}
628+
}
629+
598630
type BaseGridCSSRepeat = {
599631
type: 'REPEAT'
600632
value: Array<GridDimension>

editor/src/components/inspector/controls/advanced-grid-modal.tsx

+40
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import {
1616
import { optionalMap } from '../../../core/shared/optional-utils'
1717
import type { FlexAlignment } from 'utopia-api/core'
1818
import { FlexJustifyContent } from 'utopia-api/core'
19+
import { GridAutoColsOrRowsControlInner } from '../grid-auto-cols-or-rows-control'
20+
import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook'
21+
import { MetadataUtils } from '../../../core/model/element-metadata-utils'
22+
import { selectedViewsSelector } from '../inpector-selectors'
1923

2024
export interface AdvancedGridModalProps {
2125
id: string
@@ -103,6 +107,25 @@ export const AdvancedGridModal = React.memo((props: AdvancedGridModalProps) => {
103107
[alignContentLayoutInfo],
104108
)
105109

110+
const selectedViewsRef = useRefEditorState(selectedViewsSelector)
111+
const grid = useEditorState(
112+
Substores.metadata,
113+
(store) => {
114+
if (selectedViewsRef.current.length !== 1) {
115+
return null
116+
}
117+
return MetadataUtils.findElementByElementPath(
118+
store.editor.jsxMetadata,
119+
selectedViewsRef.current[0],
120+
)
121+
},
122+
'AdvancedGridModal grid',
123+
)
124+
125+
if (grid == null) {
126+
return null
127+
}
128+
106129
const advancedGridModal = (
107130
<InspectorModal
108131
offsetX={modalOffset.x}
@@ -191,6 +214,23 @@ export const AdvancedGridModal = React.memo((props: AdvancedGridModalProps) => {
191214
onOpenChange={toggleAlignContentDropdown}
192215
/>
193216
</UIGridRow>
217+
<UIGridRow padded variant='<-------------1fr------------->'>
218+
<span style={{ fontWeight: 600 }}>Template</span>
219+
</UIGridRow>
220+
<UIGridRow
221+
padded
222+
variant={rowVariant}
223+
className={`ignore-react-onclickoutside-${props.id}`}
224+
>
225+
<GridAutoColsOrRowsControlInner grid={grid} axis='column' label='Auto Cols' />
226+
</UIGridRow>
227+
<UIGridRow
228+
padded
229+
variant={rowVariant}
230+
className={`ignore-react-onclickoutside-${props.id}`}
231+
>
232+
<GridAutoColsOrRowsControlInner grid={grid} axis='row' label='Auto Rows' />
233+
</UIGridRow>
194234
</FlexColumn>
195235
</InspectorModal>
196236
)

editor/src/components/inspector/flex-section.spec.browser2.tsx

+13-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { selectComponentsForTest } from '../../utils/utils.test-utils'
22
import { renderTestEditorWithCode } from '../canvas/ui-jsx.test-utils'
33
import * as EP from '../../core/shared/element-path'
44
import { act, fireEvent, screen } from '@testing-library/react'
5-
import { GridAutoColsOrRowsControlTestId } from './flex-section'
5+
import { GridAutoColsOrRowsControlTestId } from './grid-auto-cols-or-rows-control'
66

77
describe('flex section', () => {
88
describe('grid dimensions', () => {
@@ -78,7 +78,10 @@ describe('flex section', () => {
7878
})
7979
describe('auto cols/rows', () => {
8080
it('can set a number', async () => {
81-
const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report')
81+
const renderResult = await renderTestEditorWithCode(
82+
gridProjectWithoutTemplate,
83+
'await-first-dom-report',
84+
)
8285
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')])
8386
const control: HTMLInputElement = await screen.findByTestId(
8487
GridAutoColsOrRowsControlTestId('column'),
@@ -89,7 +92,10 @@ describe('flex section', () => {
8992
expect(control.value).toBe('50px')
9093
})
9194
it('can set a keyword', async () => {
92-
const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report')
95+
const renderResult = await renderTestEditorWithCode(
96+
gridProjectWithoutTemplate,
97+
'await-first-dom-report',
98+
)
9399
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')])
94100
const control: HTMLInputElement = await screen.findByTestId(
95101
GridAutoColsOrRowsControlTestId('column'),
@@ -100,7 +106,10 @@ describe('flex section', () => {
100106
expect(control.value).toBe('min-content')
101107
})
102108
it('can set an expression', async () => {
103-
const renderResult = await renderTestEditorWithCode(gridProject, 'await-first-dom-report')
109+
const renderResult = await renderTestEditorWithCode(
110+
gridProjectWithoutTemplate,
111+
'await-first-dom-report',
112+
)
104113
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')])
105114
const control: HTMLInputElement = await screen.findByTestId(
106115
GridAutoColsOrRowsControlTestId('column'),

editor/src/components/inspector/flex-section.tsx

+35-112
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ import {
4848
gridCSSNumber,
4949
isCSSKeyword,
5050
isCSSNumber,
51-
isEmptyInputValue,
52-
isGridCSSNumber,
5351
printArrayGridDimensions,
5452
type GridDimension,
5553
} from './common/css-utils'
@@ -91,6 +89,12 @@ import {
9189
} from '../canvas/controls/select-mode/select-mode-hooks'
9290
import type { Axis } from '../canvas/gap-utils'
9391
import { GridExpressionInput } from '../../uuiui/inputs/grid-expression-input'
92+
import {
93+
gridDimensionDropdownKeywords,
94+
parseGridDimensionInput,
95+
useGridExpressionInputFocused,
96+
} from './grid-helpers'
97+
import { GridAutoColsOrRowsControl } from './grid-auto-cols-or-rows-control'
9498

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

240+
const autoTemplate = React.useMemo(() => {
241+
return axis === 'column'
242+
? grid.specialSizeMeasurements.containerGridPropertiesFromProps.gridAutoColumns
243+
: grid.specialSizeMeasurements.containerGridPropertiesFromProps.gridAutoRows
244+
}, [grid, axis])
245+
236246
const onUpdateDimension = React.useCallback(
237247
(index: number) => (newValue: GridDimension) => {
238248
if (template?.type !== 'DIMENSIONS') {
@@ -464,6 +474,20 @@ const TemplateDimensionControl = React.memo(
464474

465475
const dimensionsWithGeneratedIndexes = useGeneratedIndexesFromGridDimensions(values)
466476

477+
const showAutoColsOrRows = React.useMemo(() => {
478+
return (
479+
template?.type !== 'DIMENSIONS' ||
480+
template.dimensions.length === 0 ||
481+
(autoTemplate?.type === 'DIMENSIONS' &&
482+
autoTemplate.dimensions.length > 0 &&
483+
!(
484+
autoTemplate.dimensions.length === 1 &&
485+
autoTemplate.dimensions[0].type === 'KEYWORD' &&
486+
autoTemplate.dimensions[0].value.value === 'auto'
487+
))
488+
)
489+
}, [template, autoTemplate])
490+
467491
return (
468492
<div
469493
style={{
@@ -492,19 +516,20 @@ const TemplateDimensionControl = React.memo(
492516
opener={openDropdown}
493517
/>
494518
))}
495-
<AutoColsOrRowsControl grid={grid} axis={axis} />
519+
{when(
520+
showAutoColsOrRows,
521+
<GridAutoColsOrRowsControl
522+
grid={grid}
523+
axis={axis}
524+
label={axis === 'column' ? 'Auto Cols' : 'Auto Rows'}
525+
/>,
526+
)}
496527
</div>
497528
)
498529
},
499530
)
500531
TemplateDimensionControl.displayName = 'TemplateDimensionControl'
501532

502-
const gridDimensionDropdownKeywords = [
503-
{ label: 'Auto', value: cssKeyword('auto') },
504-
{ label: 'Min-Content', value: cssKeyword('min-content') },
505-
{ label: 'Max-Content', value: cssKeyword('max-content') },
506-
]
507-
508533
function AxisDimensionControl({
509534
value,
510535
index,
@@ -597,6 +622,7 @@ function AxisDimensionControl({
597622
onFocus={gridExpressionInputFocused.onFocus}
598623
onBlur={gridExpressionInputFocused.onBlur}
599624
keywords={gridDimensionDropdownKeywords}
625+
defaultValue={gridCSSKeyword(cssKeyword('auto'), null)}
600626
/>
601627
{when(
602628
(isHovered && !gridExpressionInputFocused.focused) || isOpen,
@@ -1090,106 +1116,3 @@ function useGeneratedIndexesFromGridDimensions(
10901116
return result
10911117
}, [dimensions])
10921118
}
1093-
1094-
const useGridExpressionInputFocused = () => {
1095-
const [focused, setFocused] = React.useState(false)
1096-
const onFocus = React.useCallback(() => setFocused(true), [])
1097-
const onBlur = React.useCallback(() => setFocused(false), [])
1098-
return { focused, onFocus, onBlur }
1099-
}
1100-
1101-
function parseGridDimensionInput(
1102-
value: UnknownOrEmptyInput<CSSNumber | CSSKeyword<ValidGridDimensionKeyword>>,
1103-
currentValue: GridDimension | null,
1104-
) {
1105-
if (isCSSNumber(value)) {
1106-
const maybeUnit =
1107-
currentValue != null && isGridCSSNumber(currentValue) ? currentValue.value.unit : null
1108-
return gridCSSNumber(
1109-
cssNumber(value.value, value.unit ?? maybeUnit),
1110-
currentValue?.areaName ?? null,
1111-
)
1112-
} else if (isCSSKeyword(value)) {
1113-
return gridCSSKeyword(value, currentValue?.areaName ?? null)
1114-
} else if (isEmptyInputValue(value)) {
1115-
return gridCSSKeyword(cssKeyword('auto'), currentValue?.areaName ?? null)
1116-
} else {
1117-
return null
1118-
}
1119-
}
1120-
1121-
const AutoColsOrRowsControl = React.memo(
1122-
(props: { axis: 'column' | 'row'; grid: ElementInstanceMetadata }) => {
1123-
const value = React.useMemo(() => {
1124-
const template = props.grid.specialSizeMeasurements.containerGridPropertiesFromProps
1125-
const data = props.axis === 'column' ? template.gridAutoColumns : template.gridAutoRows
1126-
if (data?.type !== 'DIMENSIONS') {
1127-
return null
1128-
}
1129-
return data.dimensions[0]
1130-
}, [props.grid, props.axis])
1131-
1132-
const dispatch = useDispatch()
1133-
1134-
const onUpdateDimension = React.useCallback(
1135-
(newDimension: GridDimension) => {
1136-
dispatch([
1137-
applyCommandsAction([
1138-
setProperty(
1139-
'always',
1140-
props.grid.elementPath,
1141-
PP.create('style', props.axis === 'column' ? 'gridAutoColumns' : 'gridAutoRows'),
1142-
printArrayGridDimensions([newDimension]),
1143-
),
1144-
]),
1145-
])
1146-
},
1147-
[props.grid, props.axis, dispatch],
1148-
)
1149-
1150-
const onUpdateNumberOrKeyword = React.useCallback(
1151-
(newValue: UnknownOrEmptyInput<CSSNumber | CSSKeyword<ValidGridDimensionKeyword>>) => {
1152-
const parsed = parseGridDimensionInput(newValue, null)
1153-
if (parsed == null) {
1154-
return
1155-
}
1156-
onUpdateDimension(parsed)
1157-
},
1158-
[onUpdateDimension],
1159-
)
1160-
1161-
const autoColsOrRowsValueFocused = useGridExpressionInputFocused()
1162-
1163-
return (
1164-
<div
1165-
style={{
1166-
display: 'grid',
1167-
gridAutoFlow: 'column',
1168-
alignItems: 'center',
1169-
gap: 6,
1170-
gridTemplateColumns: autoColsOrRowsValueFocused.focused
1171-
? '40px auto'
1172-
: `40px auto ${UtopiaTheme.layout.inputHeight.default}px`,
1173-
gridTemplateRows: '1fr',
1174-
width: '100%',
1175-
}}
1176-
>
1177-
<div>Default</div>
1178-
<GridExpressionInput
1179-
testId={GridAutoColsOrRowsControlTestId(props.axis)}
1180-
value={value ?? gridCSSKeyword(cssKeyword('auto'), null)}
1181-
onUpdateNumberOrKeyword={onUpdateNumberOrKeyword}
1182-
onUpdateDimension={onUpdateDimension}
1183-
onFocus={autoColsOrRowsValueFocused.onFocus}
1184-
onBlur={autoColsOrRowsValueFocused.onBlur}
1185-
keywords={gridDimensionDropdownKeywords}
1186-
/>
1187-
</div>
1188-
)
1189-
},
1190-
)
1191-
AutoColsOrRowsControl.displayName = 'AutoColsOrRowsControl'
1192-
1193-
export function GridAutoColsOrRowsControlTestId(axis: 'column' | 'row'): string {
1194-
return `grid-template-auto-${axis}`
1195-
}

0 commit comments

Comments
 (0)