Add vertical alignment to the GroupBlock
closes RCX-2291 flag=block_editor test plan: - start a page from scratch - change the layout from column to row on the default Group - add a couple things (icons will do) > expect them to be in a row - add a group - ad a couple things to that group > expect the new group items to be in a column > expect the original items to be at the top of the Group - on the outer group, change the vertical alignment to center > expect the original items to be cenered vertically - change the vertical alignment to end > expect the original items to be at the bottom Change-Id: I39c249a3800b95ee1dd2e03d58a5a47fd7e31861 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/355890 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Jacob DeWar <jacob.dewar@instructure.com> QA-Review: Jacob DeWar <jacob.dewar@instructure.com> Product-Review: Ed Schiebel <eschiebel@instructure.com>
This commit is contained in:
parent
334a04e585
commit
7c8cf65ff4
|
@ -33,6 +33,7 @@ const I18n = useI18nScope('block-editor')
|
|||
export const GroupBlock = (props: GroupBlockProps) => {
|
||||
const {
|
||||
alignment = GroupBlock.craft.defaultProps.alignment,
|
||||
verticalAlignment = GroupBlock.craft.defaultProps.verticalAlignment,
|
||||
layout = GroupBlock.craft.defaultProps.layout,
|
||||
resizable = GroupBlock.craft.defaultProps.resizable,
|
||||
} = props
|
||||
|
@ -44,6 +45,7 @@ export const GroupBlock = (props: GroupBlockProps) => {
|
|||
'group-block',
|
||||
`${layout}-layout`,
|
||||
`${alignment}-align`,
|
||||
`${verticalAlignment}-valign`,
|
||||
])
|
||||
const {node} = useNode((n: Node) => {
|
||||
return {
|
||||
|
@ -76,6 +78,7 @@ GroupBlock.craft = {
|
|||
displayName: I18n.t('Group'),
|
||||
defaultProps: {
|
||||
alignment: 'start',
|
||||
verticalAlignment: 'start',
|
||||
layout: 'column',
|
||||
resizable: true,
|
||||
},
|
||||
|
|
|
@ -60,15 +60,33 @@ export const GroupBlockToolbar = () => {
|
|||
[setProp]
|
||||
)
|
||||
|
||||
const renderAlignmentIcon = () => {
|
||||
switch (props.alignment) {
|
||||
const handleChangeVerticalAlignment = useCallback(
|
||||
(e, value) => {
|
||||
setProp((prps: GroupBlockProps) => {
|
||||
prps.verticalAlignment = value as GroupAlignment
|
||||
})
|
||||
},
|
||||
[setProp]
|
||||
)
|
||||
|
||||
const rotate = {
|
||||
rotate: '90deg',
|
||||
}
|
||||
|
||||
const renderAlignmentIcon = (vertical: boolean) => {
|
||||
let icon
|
||||
const align = vertical ? props.verticalAlignment : props.alignment
|
||||
switch (align) {
|
||||
case 'start':
|
||||
return <IconTextStartLine size="x-small" />
|
||||
icon = <IconTextStartLine size="x-small" />
|
||||
break
|
||||
case 'center':
|
||||
return <IconTextCenteredLine size="x-small" />
|
||||
icon = <IconTextCenteredLine size="x-small" />
|
||||
break
|
||||
case 'end':
|
||||
return <IconTextEndLine size="x-small" />
|
||||
icon = <IconTextEndLine size="x-small" />
|
||||
}
|
||||
return vertical ? <span style={rotate}>{icon}</span> : icon
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -93,15 +111,16 @@ export const GroupBlockToolbar = () => {
|
|||
{I18n.t('Row')}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
|
||||
<Menu
|
||||
trigger={
|
||||
<IconButton
|
||||
size="small"
|
||||
withBorder={false}
|
||||
withBackground={false}
|
||||
screenReaderLabel={I18n.t('Align')}
|
||||
screenReaderLabel={I18n.t('Align Horizontally')}
|
||||
>
|
||||
{renderAlignmentIcon()}
|
||||
{renderAlignmentIcon(false)}
|
||||
</IconButton>
|
||||
}
|
||||
onSelect={handleChangeAlignment}
|
||||
|
@ -112,19 +131,66 @@ export const GroupBlockToolbar = () => {
|
|||
<Text>{I18n.t('Align to start')}</Text>
|
||||
</Flex>
|
||||
</Menu.Item>
|
||||
<Menu.Item type="checkbox" value="center" defaultSelected={props.layout === 'center'}>
|
||||
<Menu.Item type="checkbox" value="center" defaultSelected={props.alignment === 'center'}>
|
||||
<Flex gap="x-small">
|
||||
<IconTextCenteredLine size="x-small" />
|
||||
<Text>{I18n.t('Align to center')}</Text>
|
||||
</Flex>
|
||||
</Menu.Item>
|
||||
<Menu.Item type="checkbox" value="end" defaultSelected={props.layout === 'end'}>
|
||||
<Menu.Item type="checkbox" value="end" defaultSelected={props.alignment === 'end'}>
|
||||
<Flex gap="x-small">
|
||||
<IconTextEndLine size="x-small" />
|
||||
<Text>{I18n.t('Align to end')}</Text>
|
||||
</Flex>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
|
||||
<Menu
|
||||
trigger={
|
||||
<IconButton
|
||||
size="small"
|
||||
withBorder={false}
|
||||
withBackground={false}
|
||||
screenReaderLabel={I18n.t('Align Vertically')}
|
||||
>
|
||||
{renderAlignmentIcon(true)}
|
||||
</IconButton>
|
||||
}
|
||||
onSelect={handleChangeVerticalAlignment}
|
||||
>
|
||||
<Menu.Item
|
||||
type="checkbox"
|
||||
value="start"
|
||||
defaultSelected={props.verticalAlignment === 'start'}
|
||||
>
|
||||
<Flex gap="x-small">
|
||||
<span style={rotate}>
|
||||
<IconTextStartLine size="x-small" />
|
||||
</span>
|
||||
<Text>{I18n.t('Align to start')}</Text>
|
||||
</Flex>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
type="checkbox"
|
||||
value="center"
|
||||
defaultSelected={props.verticalAlignment === 'center'}
|
||||
>
|
||||
<Flex gap="x-small">
|
||||
<span style={rotate}>
|
||||
<IconTextCenteredLine size="x-small" />
|
||||
</span>
|
||||
<Text>{I18n.t('Align to center')}</Text>
|
||||
</Flex>
|
||||
</Menu.Item>
|
||||
<Menu.Item type="checkbox" value="end" defaultSelected={props.verticalAlignment === 'end'}>
|
||||
<Flex gap="x-small">
|
||||
<span style={rotate}>
|
||||
<IconTextEndLine size="x-small" />
|
||||
</span>
|
||||
<Text>{I18n.t('Align to end')}</Text>
|
||||
</Flex>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -63,4 +63,16 @@ describe('ColumnsSection', () => {
|
|||
expect(container.querySelector('.group-block')).toBeInTheDocument()
|
||||
expect(container.querySelector('.group-block')).toHaveClass('row-layout')
|
||||
})
|
||||
|
||||
it('should render with center horizontal alignment', () => {
|
||||
const {container} = renderBlock({alignment: 'center'})
|
||||
expect(container.querySelector('.group-block')).toBeInTheDocument()
|
||||
expect(container.querySelector('.group-block')).toHaveClass('center-align')
|
||||
})
|
||||
|
||||
it('should render with center vertical alignment', () => {
|
||||
const {container} = renderBlock({verticalAlignment: 'center'})
|
||||
expect(container.querySelector('.group-block')).toBeInTheDocument()
|
||||
expect(container.querySelector('.group-block')).toHaveClass('center-valign')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -49,34 +49,104 @@ describe('GroupBlockToolbar', () => {
|
|||
const {getByText} = render(<GroupBlockToolbar />)
|
||||
|
||||
expect(getByText('Layout direction')).toBeInTheDocument()
|
||||
expect(getByText('Align Horizontally')).toBeInTheDocument()
|
||||
expect(getByText('Align Vertically')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('checks the right layout direction', async () => {
|
||||
const {getByText} = render(<GroupBlockToolbar />)
|
||||
describe('layout direction', () => {
|
||||
it('checks the right layout direction', async () => {
|
||||
const {getByText} = render(<GroupBlockToolbar />)
|
||||
|
||||
const btn = getByText('Layout direction').closest('button') as HTMLButtonElement
|
||||
await userEvent.click(btn)
|
||||
const btn = getByText('Layout direction').closest('button') as HTMLButtonElement
|
||||
await userEvent.click(btn)
|
||||
|
||||
const colMenuItem = screen.getByText('Column')
|
||||
const rowMenuItem = screen.getByText('Row')
|
||||
const colMenuItem = screen.getByText('Column')
|
||||
const rowMenuItem = screen.getByText('Row')
|
||||
|
||||
expect(colMenuItem).toBeInTheDocument()
|
||||
expect(rowMenuItem).toBeInTheDocument()
|
||||
expect(colMenuItem).toBeInTheDocument()
|
||||
expect(rowMenuItem).toBeInTheDocument()
|
||||
|
||||
const li = colMenuItem.closest('li') as HTMLLIElement
|
||||
expect(li.querySelector('svg[name="IconCheck"]')).toBeInTheDocument()
|
||||
const li = colMenuItem.closest('li') as HTMLLIElement
|
||||
expect(li.querySelector('svg[name="IconCheck"]')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('changes the direction prop', async () => {
|
||||
const {getByText} = render(<GroupBlockToolbar />)
|
||||
|
||||
const btn = getByText('Layout direction').closest('button') as HTMLButtonElement
|
||||
await userEvent.click(btn)
|
||||
|
||||
const rowMenuItem = screen.getByText('Row')
|
||||
await userEvent.click(rowMenuItem)
|
||||
|
||||
expect(mockSetProp).toHaveBeenCalled()
|
||||
expect(props.layout).toBe('row')
|
||||
})
|
||||
})
|
||||
|
||||
it('changes the direction prop', async () => {
|
||||
const {getByText} = render(<GroupBlockToolbar />)
|
||||
describe('horizontal alignment', () => {
|
||||
it('checks the right alignment', async () => {
|
||||
const {getByText} = render(<GroupBlockToolbar />)
|
||||
|
||||
const btn = getByText('Layout direction').closest('button') as HTMLButtonElement
|
||||
await userEvent.click(btn)
|
||||
const btn = getByText('Align Horizontally').closest('button') as HTMLButtonElement
|
||||
await userEvent.click(btn)
|
||||
|
||||
const rowMenuItem = screen.getByText('Row')
|
||||
await userEvent.click(rowMenuItem)
|
||||
const startMenuItem = screen.getByText('Align to start')
|
||||
const centerMenuItem = screen.getByText('Align to center')
|
||||
const endMenuItem = screen.getByText('Align to end')
|
||||
|
||||
expect(mockSetProp).toHaveBeenCalled()
|
||||
expect(props.layout).toBe('row')
|
||||
expect(startMenuItem).toBeInTheDocument()
|
||||
expect(centerMenuItem).toBeInTheDocument()
|
||||
expect(endMenuItem).toBeInTheDocument()
|
||||
|
||||
const startLi = startMenuItem.closest('li') as HTMLLIElement
|
||||
expect(startLi.querySelector('svg[name="IconCheck"]')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('changes the alignment prop', async () => {
|
||||
const {getByText} = render(<GroupBlockToolbar />)
|
||||
|
||||
const btn = getByText('Align Horizontally').closest('button') as HTMLButtonElement
|
||||
await userEvent.click(btn)
|
||||
|
||||
const centerMenuItem = screen.getByText('Align to center')
|
||||
await userEvent.click(centerMenuItem)
|
||||
|
||||
expect(mockSetProp).toHaveBeenCalled()
|
||||
expect(props.alignment).toBe('center')
|
||||
})
|
||||
})
|
||||
|
||||
describe('vertical alignment', () => {
|
||||
it('checks the right alignment', async () => {
|
||||
const {getByText} = render(<GroupBlockToolbar />)
|
||||
|
||||
const btn = getByText('Align Vertically').closest('button') as HTMLButtonElement
|
||||
await userEvent.click(btn)
|
||||
|
||||
const startMenuItem = screen.getByText('Align to start')
|
||||
const centerMenuItem = screen.getByText('Align to center')
|
||||
const endMenuItem = screen.getByText('Align to end')
|
||||
|
||||
expect(startMenuItem).toBeInTheDocument()
|
||||
expect(centerMenuItem).toBeInTheDocument()
|
||||
expect(endMenuItem).toBeInTheDocument()
|
||||
|
||||
const startLi = startMenuItem.closest('li') as HTMLLIElement
|
||||
expect(startLi.querySelector('svg[name="IconCheck"]')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('changes the vertical alignment prop', async () => {
|
||||
const {getByText} = render(<GroupBlockToolbar />)
|
||||
|
||||
const btn = getByText('Align Vertically').closest('button') as HTMLButtonElement
|
||||
await userEvent.click(btn)
|
||||
|
||||
const centerMenuItem = screen.getByText('Align to center')
|
||||
await userEvent.click(centerMenuItem)
|
||||
|
||||
expect(mockSetProp).toHaveBeenCalled()
|
||||
expect(props.verticalAlignment).toBe('center')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -22,5 +22,6 @@ export type GroupAlignment = 'start' | 'center' | 'end'
|
|||
export type GroupBlockProps = {
|
||||
layout?: GroupLayout
|
||||
alignment?: GroupAlignment
|
||||
verticalAlignment?: GroupAlignment
|
||||
resizable?: boolean
|
||||
}
|
||||
|
|
|
@ -312,22 +312,31 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
&.row-layout {
|
||||
&> .no-sections {
|
||||
&> .group-block__inner {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
&.center-align > .no-sections {
|
||||
&.center-align > .group-block__inner {
|
||||
justify-content: center;
|
||||
}
|
||||
&.end-align > .no-sections {
|
||||
&.end-align > .group-block__inner {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
&.start-valign > .group-block__inner {
|
||||
align-items: flex-start;
|
||||
}
|
||||
&.center-valign > .group-block__inner {
|
||||
align-items: center;
|
||||
}
|
||||
&.end-valign > .group-block__inner {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
&.column-layout {
|
||||
&.center-align > .no-sections {
|
||||
&.center-align > .group-block__inner {
|
||||
align-items: center;
|
||||
}
|
||||
&.end-align > .no-sections {
|
||||
&.end-align > .group-block__inner {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue