Skip to content

Commit

Permalink
Disable alignment buttons/groups based on element (#6461)
Browse files Browse the repository at this point in the history
**Problem:**

Following up to #6459, the
alignment buttons should be disabled following a comprehensive set of
rules.

**Fix:**

This PR updates the logic to disable alignment buttons and groups
following the following logic.

- Grid children all have alignment buttons enabled
- Flex children have alignment buttons enabled for the orientation
opposite to their parent's flex direction
- Absolute elements have alignment buttons enabled, if they are not
storyboard children
- Flow elements have all alignment buttons disabled

Fixes #6460
  • Loading branch information
ruggi authored Oct 4, 2024
1 parent 03ec7f4 commit e1f1b8a
Show file tree
Hide file tree
Showing 4 changed files with 373 additions and 5 deletions.
290 changes: 290 additions & 0 deletions editor/src/components/inspector/alignment-buttons.spec.browser2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
import { MetadataUtils } from '../../core/model/element-metadata-utils'
import * as EP from '../../core/shared/element-path'
import { renderTestEditorWithCode } from '../canvas/ui-jsx.test-utils'
import { isAlignmentGroupDisabled } from './use-disable-alignment'

describe('alignment buttons', () => {
describe('isAlignmentGroupDisabled', () => {
it('enables buttons for grid cells', async () => {
const renderResult = await renderTestEditorWithCode(projectCode, 'await-first-dom-report')
const metadata = renderResult.getEditorState().editor.jsxMetadata

expect(
isAlignmentGroupDisabled(
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/grid/grid-child')),
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/grid')),
'horizontal',
[],
),
).toBe(false)
})
it('enables buttons for flex only in the opposite direction', async () => {
const renderResult = await renderTestEditorWithCode(projectCode, 'await-first-dom-report')
const metadata = renderResult.getEditorState().editor.jsxMetadata

// flex-direction: row
{
expect(
isAlignmentGroupDisabled(
MetadataUtils.findElementByElementPath(
metadata,
EP.fromString('sb/flex-horiz/flex-horiz-child'),
),
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flex-horiz')),
'vertical',
[],
),
).toBe(false)
expect(
isAlignmentGroupDisabled(
MetadataUtils.findElementByElementPath(
metadata,
EP.fromString('sb/flex-horiz/flex-horiz-child'),
),
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flex-horiz')),
'horizontal',
[],
),
).toBe(true)
}

// flex-direction: column
{
expect(
isAlignmentGroupDisabled(
MetadataUtils.findElementByElementPath(
metadata,
EP.fromString('sb/flex-vert/flex-vert-child'),
),
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flex-vert')),
'horizontal',
[],
),
).toBe(false)
expect(
isAlignmentGroupDisabled(
MetadataUtils.findElementByElementPath(
metadata,
EP.fromString('sb/flex-vert/flex-vert-child'),
),
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flex-vert')),
'vertical',
[],
),
).toBe(true)
}
})
it('enables buttons for absolute only if not storyboard children', async () => {
const renderResult = await renderTestEditorWithCode(projectCode, 'await-first-dom-report')
const metadata = renderResult.getEditorState().editor.jsxMetadata

expect(
isAlignmentGroupDisabled(
MetadataUtils.findElementByElementPath(
metadata,
EP.fromString('sb/flow/flow-child-absolute'),
),
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flow')),
'horizontal',
[],
),
).toBe(false)

expect(
isAlignmentGroupDisabled(
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flow')),
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb')),
'horizontal',
[],
),
).toBe(true)

expect(
isAlignmentGroupDisabled(
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flow')),
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb')),
'horizontal',
[EP.fromString('sb/flow'), EP.fromString('sb/grid')],
),
).toBe(false)
})
it('disables buttons for flow', async () => {
const renderResult = await renderTestEditorWithCode(projectCode, 'await-first-dom-report')
const metadata = renderResult.getEditorState().editor.jsxMetadata

expect(
isAlignmentGroupDisabled(
MetadataUtils.findElementByElementPath(
metadata,
EP.fromString('sb/flow/flow-child-relative'),
),
MetadataUtils.findElementByElementPath(metadata, EP.fromString('sb/flow')),
'horizontal',
[],
),
).toBe(true)
})
})
})

const projectCode = `import * as React from 'react'
import { Storyboard } from 'utopia-api'
export var storyboard = (
<Storyboard data-uid='sb'>
<div
data-uid='grid'
style={{
backgroundColor: '#aaaaaa33',
position: 'absolute',
width: 318,
height: 288,
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
gridTemplateRows: '1fr 1fr 1fr',
padding: 20,
gridGap: 20,
justifyItems: 'flex-end',
alignContent: 'center',
}}
>
<div
data-uid='grid'data-uid='grid-child'
style={{
backgroundColor: '#aaaaaa33',
justifySelf: 'stretch',
gridColumn: 3,
gridRow: 2,
alignSelf: 'stretch',
}}
/>
</div>
<div
data-uid='flow'
style={{
backgroundColor: '#aaaaaa33',
position: 'absolute',
left: 339,
top: 0,
width: 282,
height: 247,
contain: 'layout',
}}
>
<div
data-uid='flow-child-relative'
style={{
backgroundColor: '#aaaaaa33',
left: 21,
top: 31,
width: 57,
height: 59,
}}
/>
<div
style={{
backgroundColor: '#aaaaaa33',
left: 22,
top: 99,
width: 57,
height: 59,
}}
/>
<div
data-uid='flow-child-absolute'
style={{
backgroundColor: '#aaaaaa33',
position: 'absolute',
left: 153,
top: 94,
width: 57,
height: 59,
}}
/>
</div>
<div
data-uid='flex-horiz'
style={{
backgroundColor: '#aaaaaa33',
position: 'absolute',
left: 0,
top: 302,
width: 318,
height: 155,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 10,
}}
>
<div
data-uid='flex-horiz-child'
style={{
backgroundColor: '#aaaaaa33',
width: 57,
height: 37,
contain: 'layout',
}}
/>
<div
style={{
backgroundColor: '#aaaaaa33',
width: 57,
height: 37,
contain: 'layout',
}}
/>
<div
style={{
backgroundColor: '#aaaaaa33',
width: 57,
height: 37,
contain: 'layout',
}}
/>
</div>
<div
data-uid='flex-vert'
style={{
backgroundColor: '#aaaaaa33',
position: 'absolute',
left: 339,
top: 307,
width: 164,
height: 300,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
gap: 10,
}}
>
<div
data-uid='flex-vert-child'
style={{
backgroundColor: '#aaaaaa33',
width: 54.5,
height: 66,
contain: 'layout',
}}
/>
<div
style={{
backgroundColor: '#aaaaaa33',
width: 54.5,
height: 66,
contain: 'layout',
}}
/>
<div
style={{
backgroundColor: '#aaaaaa33',
width: 54.5,
height: 66,
contain: 'layout',
}}
/>
</div>
</Storyboard>
)
`
11 changes: 7 additions & 4 deletions editor/src/components/inspector/alignment-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { Substores, useEditorState, useRefEditorState } from '../editor/store/st
import { getControlStyles } from './common/control-styles'
import { OptionChainControl } from './controls/option-chain-control'
import { UIGridRow } from './widgets/ui-grid-row'
import { useDisableAlignment } from './use-disable-alignment'

type ActiveAlignments = { [key in Alignment]: boolean }

Expand All @@ -34,7 +35,6 @@ export const AlignmentButtons = React.memo(() => {

const selectedViews = useRefEditorState((store) => store.editor.selectedViews)

const disableAlign = selectedViews.current.length === 0
const disableDistribute = selectedViews.current.length < 3

const activeAlignments = useActiveAlignments()
Expand Down Expand Up @@ -171,6 +171,9 @@ export const AlignmentButtons = React.memo(() => {
: null
}, [activeAlignments])

const disableJustify = useDisableAlignment(selectedViews.current, 'horizontal')
const disableAlign = useDisableAlignment(selectedViews.current, 'vertical')

return (
<UIGridRow padded={false} variant='<--1fr--><--1fr-->|22px|'>
<OptionChainControl
Expand All @@ -186,19 +189,19 @@ export const AlignmentButtons = React.memo(() => {
value: 'left',
icon: { category: 'inspector', type: 'justify-start' },
forceCallOnSubmitValue: true,
disabled: disableAlign,
disabled: disableJustify,
},
{
value: 'hcenter',
icon: { category: 'inspector', type: 'justify-center' },
forceCallOnSubmitValue: true,
disabled: disableAlign,
disabled: disableJustify,
},
{
value: 'right',
icon: { category: 'inspector', type: 'justify-end' },
forceCallOnSubmitValue: true,
disabled: disableAlign,
disabled: disableJustify,
},
]}
/>
Expand Down
3 changes: 2 additions & 1 deletion editor/src/components/inspector/controls/option-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ export const OptionControl: React.FunctionComponent<
},
},
'&:hover': {
opacity: props.controlStatus === 'disabled' ? undefined : 1,
opacity:
controlOptions.disabled || props.controlStatus === 'disabled' ? undefined : 1,
},
'.control-option-icon-component': {
opacity: 0.7,
Expand Down
Loading

0 comments on commit e1f1b8a

Please sign in to comment.