Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional auto cols/rows input in the inspector, auto template in the advanced modal #6541

Merged
merged 3 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -190,6 +213,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'

const axisDropdownMenuButton = 'axisDropdownMenuButton'

Expand Down Expand Up @@ -235,6 +239,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 @@ -466,6 +476,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 @@ -494,19 +518,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 @@ -599,6 +624,7 @@ function AxisDimensionControl({
onFocus={gridExpressionInputFocused.onFocus}
onBlur={gridExpressionInputFocused.onBlur}
keywords={gridDimensionDropdownKeywords}
defaultValue={gridCSSKeyword(cssKeyword('auto'), null)}
/>
{unless(
gridExpressionInputFocused.focused,
Expand Down Expand Up @@ -1092,106 +1118,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
Loading