Skip to content

Commit

Permalink
Add table block column alignment (#16111)
Browse files Browse the repository at this point in the history
* Initial step at adding alignment options in table block edit component

Fix block.json attributes

Add cell alignment to save function

Add tests for table cell alignment

Tidy up test comments

Update JSDocs for new functions

Add unit tests

Use classnames for cell alignment

Use shorthand property

Remove alignment styles in favour of global styles

Update copy of alignment buttons

Rework cell selection border to use an actual border around a pseudoelement instead of box-shadow

* Make cell alignment work across columns instead of individual cells

Add jsdocs

Inherit the alignment property of the first cell in the column when adding new rows

Update e2e tests

Use consistent letter casing

* Address review feedback

Update params of getCellAttribute function

Do nothing if insertRows is unable to determine the correct cellCount for the insertion
  • Loading branch information
talldan authored Jul 31, 2019
1 parent 9160749 commit c59cda8
Show file tree
Hide file tree
Showing 8 changed files with 656 additions and 181 deletions.
12 changes: 10 additions & 2 deletions packages/block-editor/src/components/alignment-toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ const DEFAULT_ALIGNMENT_CONTROLS = [
},
];

export function AlignmentToolbar( { value, onChange, alignmentControls = DEFAULT_ALIGNMENT_CONTROLS, isCollapsed = true } ) {
export function AlignmentToolbar( props ) {
const {
value,
onChange,
alignmentControls = DEFAULT_ALIGNMENT_CONTROLS,
label = __( 'Change text alignment' ),
isCollapsed = true,
} = props;

function applyOrUnset( align ) {
return () => onChange( value === align ? undefined : align );
}
Expand All @@ -38,7 +46,7 @@ export function AlignmentToolbar( { value, onChange, alignmentControls = DEFAULT
<Toolbar
isCollapsed={ isCollapsed }
icon={ activeAlignment ? activeAlignment.icon : 'editor-alignleft' }
label={ __( 'Change text alignment' ) }
label={ label }
controls={ alignmentControls.map( ( control ) => {
const { align } = control;
const isActive = ( value === align );
Expand Down
15 changes: 15 additions & 0 deletions packages/block-library/src/table/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
"type": "string",
"source": "attribute",
"attribute": "scope"
},
"align": {
"type": "string",
"source": "attribute",
"attribute": "data-align"
}
}
}
Expand Down Expand Up @@ -64,6 +69,11 @@
"type": "string",
"source": "attribute",
"attribute": "scope"
},
"align": {
"type": "string",
"source": "attribute",
"attribute": "data-align"
}
}
}
Expand Down Expand Up @@ -94,6 +104,11 @@
"type": "string",
"source": "attribute",
"attribute": "scope"
},
"align": {
"type": "string",
"source": "attribute",
"attribute": "data-align"
}
}
}
Expand Down
169 changes: 126 additions & 43 deletions packages/block-library/src/table/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
PanelColorSettings,
createCustomColorsHOC,
BlockIcon,
AlignmentToolbar,
} from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import {
Expand All @@ -31,13 +32,15 @@ import {
*/
import {
createTable,
updateCellContent,
updateSelectedCell,
getCellAttribute,
insertRow,
deleteRow,
insertColumn,
deleteColumn,
toggleSection,
isEmptyTableSection,
isCellSelected,
} from './state';
import icon from './icon';

Expand All @@ -64,6 +67,24 @@ const BACKGROUND_COLORS = [
},
];

const ALIGNMENT_CONTROLS = [
{
icon: 'editor-alignleft',
title: __( 'Align Column Left' ),
align: 'left',
},
{
icon: 'editor-aligncenter',
title: __( 'Align Column Center' ),
align: 'center',
},
{
icon: 'editor-alignright',
title: __( 'Align Column Right' ),
align: 'right',
},
];

const withCustomBackgroundColors = createCustomColorsHOC( BACKGROUND_COLORS );

export class TableEdit extends Component {
Expand All @@ -87,6 +108,8 @@ export class TableEdit extends Component {
this.onDeleteColumn = this.onDeleteColumn.bind( this );
this.onToggleHeaderSection = this.onToggleHeaderSection.bind( this );
this.onToggleFooterSection = this.onToggleFooterSection.bind( this );
this.onChangeColumnAlignment = this.onChangeColumnAlignment.bind( this );
this.getCellAlignment = this.getCellAlignment.bind( this );

this.state = {
initialRowCount: 2,
Expand Down Expand Up @@ -156,14 +179,73 @@ export class TableEdit extends Component {
}

const { attributes, setAttributes } = this.props;
const { section, rowIndex, columnIndex } = selectedCell;

setAttributes( updateCellContent( attributes, {
section,
rowIndex,
columnIndex,
content,
} ) );
setAttributes( updateSelectedCell(
attributes,
selectedCell,
( cellAttributes ) => ( { ...cellAttributes, content } ),
) );
}

/**
* Align text within the a column.
*
* @param {string} align The new alignment to apply to the column.
*/
onChangeColumnAlignment( align ) {
const { selectedCell } = this.state;

if ( ! selectedCell ) {
return;
}

// Convert the cell selection to a column selection so that alignment
// is applied to the entire column.
const columnSelection = {
type: 'column',
columnIndex: selectedCell.columnIndex,
};

const { attributes, setAttributes } = this.props;
const newAttributes = updateSelectedCell(
attributes,
columnSelection,
( cellAttributes ) => ( { ...cellAttributes, align } ),
);
setAttributes( newAttributes );
}

/**
* Get the alignment of the currently selected cell.
*
* @return {string} The new alignment to apply to the column.
*/
getCellAlignment() {
const { selectedCell } = this.state;

if ( ! selectedCell ) {
return;
}

const { attributes } = this.props;

return getCellAttribute( attributes, selectedCell, 'align' );
}

/**
* Add or remove a `head` table section.
*/
onToggleHeaderSection() {
const { attributes, setAttributes } = this.props;
setAttributes( toggleSection( attributes, 'head' ) );
}

/**
* Add or remove a `foot` table section.
*/
onToggleFooterSection() {
const { attributes, setAttributes } = this.props;
setAttributes( toggleSection( attributes, 'foot' ) );
}

/**
Expand All @@ -179,11 +261,11 @@ export class TableEdit extends Component {
}

const { attributes, setAttributes } = this.props;
const { section, rowIndex } = selectedCell;
const { sectionName, rowIndex } = selectedCell;

this.setState( { selectedCell: null } );
setAttributes( insertRow( attributes, {
section,
sectionName,
rowIndex: rowIndex + delta,
} ) );
}
Expand All @@ -202,16 +284,6 @@ export class TableEdit extends Component {
this.onInsertRow( 1 );
}

onToggleHeaderSection() {
const { attributes, setAttributes } = this.props;
setAttributes( toggleSection( attributes, 'head' ) );
}

onToggleFooterSection() {
const { attributes, setAttributes } = this.props;
setAttributes( toggleSection( attributes, 'foot' ) );
}

/**
* Deletes the currently selected row.
*/
Expand All @@ -223,10 +295,10 @@ export class TableEdit extends Component {
}

const { attributes, setAttributes } = this.props;
const { section, rowIndex } = selectedCell;
const { sectionName, rowIndex } = selectedCell;

this.setState( { selectedCell: null } );
setAttributes( deleteRow( attributes, { section, rowIndex } ) );
setAttributes( deleteRow( attributes, { sectionName, rowIndex } ) );
}

/**
Expand Down Expand Up @@ -275,23 +347,28 @@ export class TableEdit extends Component {
}

const { attributes, setAttributes } = this.props;
const { section, columnIndex } = selectedCell;
const { sectionName, columnIndex } = selectedCell;

this.setState( { selectedCell: null } );
setAttributes( deleteColumn( attributes, { section, columnIndex } ) );
setAttributes( deleteColumn( attributes, { sectionName, columnIndex } ) );
}

/**
* Creates an onFocus handler for a specified cell.
*
* @param {Object} selectedCell Object with `section`, `rowIndex`, and
* @param {Object} cellLocation Object with `section`, `rowIndex`, and
* `columnIndex` properties.
*
* @return {Function} Function to call on focus.
*/
createOnFocus( selectedCell ) {
createOnFocus( cellLocation ) {
return () => {
this.setState( { selectedCell } );
this.setState( {
selectedCell: {
...cellLocation,
type: 'cell',
},
} );
};
}

Expand Down Expand Up @@ -351,32 +428,31 @@ export class TableEdit extends Component {
*
* @return {Object} React element for the section.
*/
renderSection( { type, rows } ) {
renderSection( { name, rows } ) {
if ( isEmptyTableSection( rows ) ) {
return null;
}

const Tag = `t${ type }`;
const Tag = `t${ name }`;
const { selectedCell } = this.state;

return (
<Tag>
{ rows.map( ( { cells }, rowIndex ) => (
<tr key={ rowIndex }>
{ cells.map( ( { content, tag: CellTag, scope }, columnIndex ) => {
const isSelected = selectedCell && (
type === selectedCell.section &&
rowIndex === selectedCell.rowIndex &&
columnIndex === selectedCell.columnIndex
);

const cell = {
section: type,
{ cells.map( ( { content, tag: CellTag, scope, align }, columnIndex ) => {
const cellLocation = {
sectionName: name,
rowIndex,
columnIndex,
};

const cellClasses = classnames( { 'is-selected': isSelected } );
const isSelected = isCellSelected( cellLocation, selectedCell );

const cellClasses = classnames( {
'is-selected': isSelected,
[ `has-text-align-${ align }` ]: align,
} );

return (
<CellTag
Expand All @@ -388,7 +464,7 @@ export class TableEdit extends Component {
className="wp-block-table__cell-content"
value={ content }
onChange={ this.onChange }
unstableOnFocus={ this.createOnFocus( cell ) }
unstableOnFocus={ this.createOnFocus( cellLocation ) }
/>
</CellTag>
);
Expand Down Expand Up @@ -467,6 +543,13 @@ export class TableEdit extends Component {
controls={ this.getTableControls() }
/>
</Toolbar>
<AlignmentToolbar
label={ __( 'Change column alignment' ) }
alignmentControls={ ALIGNMENT_CONTROLS }
value={ this.getCellAlignment() }
onChange={ ( nextAlign ) => this.onChangeColumnAlignment( nextAlign ) }
onHover={ this.onHoverAlignment }
/>
</BlockControls>
<InspectorControls>
<PanelBody title={ __( 'Table Settings' ) } className="blocks-table-settings">
Expand Down Expand Up @@ -502,9 +585,9 @@ export class TableEdit extends Component {
</InspectorControls>
<figure className={ className }>
<table className={ tableClasses }>
<Section type="head" rows={ head } />
<Section type="body" rows={ body } />
<Section type="foot" rows={ foot } />
<Section name="head" rows={ head } />
<Section name="body" rows={ body } />
<Section name="foot" rows={ foot } />
</table>
</figure>
</>
Expand Down
24 changes: 16 additions & 8 deletions packages/block-library/src/table/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,22 @@ export default function save( { attributes } ) {
<Tag>
{ rows.map( ( { cells }, rowIndex ) => (
<tr key={ rowIndex }>
{ cells.map( ( { content, tag, scope }, cellIndex ) =>
<RichText.Content
tagName={ tag }
value={ content }
key={ cellIndex }
scope={ tag === 'th' ? scope : undefined }
/>
) }
{ cells.map( ( { content, tag, scope, align }, cellIndex ) => {
const cellClasses = classnames( {
[ `has-text-align-${ align }` ]: align,
} );

return (
<RichText.Content
className={ cellClasses ? cellClasses : undefined }
data-align={ align }
tagName={ tag }
value={ content }
key={ cellIndex }
scope={ tag === 'th' ? scope : undefined }
/>
);
} ) }
</tr>
) ) }
</Tag>
Expand Down
Loading

0 comments on commit c59cda8

Please sign in to comment.