-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Add table block column alignment #16111
Conversation
63a55e4
to
61f19ed
Compare
I just tested this and it works wonderfully! Thanks, @talldan! 👍 from a design perspective. |
Thanks! I think I'll switch this to using classnames as #16035 does, as that's now been merged. |
Should this apply to the whole table, per column, or on individual cells? |
Great question, @mtias. Right now the block only allows selection of a single cell at a time. I would love to see selections happen across rows and columns. Once this works, we can look at alignment across these. |
61f19ed
to
7fdde45
Compare
This is my take on it as well. I've refactored this to use classes, so it's ready for a code review 👍 |
7fdde45
to
e4cdc2f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seesm to be in a good shape. I left some minor comments.
"align": { | ||
"type": "string", | ||
"source": "attribute", | ||
"attribute": "data-align" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. since it applied as a class, makes me wonder if at some point, we'd want "classNames" sources. That said, it can be complex and I know it's a deliberate decision to restrict the number of sources available. css @aduth
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think we'd considered once before a source serialized as the presence of a class name. Personally I don't have much of an issue with the data-
attributes; they can be styled just as well, are a bit easier to parse, and fit well as serving the purpose of a data attribute.
Custom data attributes are intended to store custom data, state, annotations, and similar, private to the page or application, for which there are no more appropriate attributes or elements.
That being said, I'm not really sure why we bother to serialize this in the HTML at all, if we're not using it for anything like styling? Why not just embed it within the comment delimiters (i.e. omit source
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aduth As this attribute is in a query
, my understanding is that source
can't be omitted. A classNames
source could be useful in this use case.
An argument against data-
attributes is that it's double storing data, but then it's not too much of a problem with the reactive nature of blocks, it's not like there's extra business logic to keep them in sync.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use the class at all? Can we not just use the data attribute?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some global styles for text alignment:
gutenberg/packages/block-library/src/style.scss
Lines 150 to 161 in 4ac7dc9
// Text alignments. | |
.has-text-align-center { | |
text-align: center; | |
} | |
.has-text-align-left { | |
text-align: left; | |
} | |
.has-text-align-right { | |
text-align: right; | |
} |
Seems better to be able to use those and not have extra styles in the table block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some strong opinion about the user experience of this, sorry.
- We should only allow table cells to be left- or right-aligned. The button can be a simple toggle. Center alignment in tables doesn't make so much sense. What kind of data will you align like that? The general rule of thumb is that textual data should be left-aligned, and numerical data should be right-aligned.
- Another table "rule" is that each column should have the same alignment. This not only looks better, it easier for the user to align the entire column at once.
I think center alignment can be perfectly fine in some cases, and we should not dictate that. However, I think that single cell alignment is a bit excessive. I'd rather have a text alignment toolbar button that applies to the whole table at once (since columns is a bit more complicated). |
* | ||
* @return {boolean} True if the cell is selected, false otherwise. | ||
*/ | ||
export function isCellSelected( cellLocation, selection ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not have isCellSelected
and isColumnSelected
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isColumnSelected
would only be used in one place, so it seems unnecessary at this stage to have two separate functions.
I'm exploring this more in #16493, where I do have separate functions for different types of selections as well as the same isCellSelected
for easily checking whether a given cell is in any type of selection:
gutenberg/packages/block-library/src/table/state.js
Lines 221 to 244 in ec1aa43
/** | |
* Determines if a cell is selected. | |
* | |
* @param {Object} cellLocation The cell to check. | |
* @param {Object} selection The selection data. | |
* | |
* @return {boolean} True if the cell is within a selection, false otherwise. | |
*/ | |
export function isCellSelected( cellLocation, selection ) { | |
if ( ! cellLocation || ! selection ) { | |
return false; | |
} | |
switch ( selection.type ) { | |
case 'cell': | |
return hasSingleCellSelection( cellLocation, selection ); | |
case 'table': | |
return true; | |
case 'row': | |
return hasRowSelection( cellLocation, selection ); | |
case 'column': | |
return hasColumnSelection( cellLocation, selection ); | |
} | |
} |
It'll be an easy refactor from this to that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine, but then I wouldn't call it isCellSelected
? That's misleading if you're checking if a column is selected. isCellSelected( ..., 'column' )
doesn't make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, yeah, that's good feedback. I can work on a few naming improvements in future PRs as this functionality develops. Something like isCellWithinSelection
might be more appropriate.
Thanks for the reviews btw!
} ) ), | ||
cells: times( cellCount, ( index ) => { | ||
const firstCellInColumn = get( firstRow, [ 'cells', index ], {} ); | ||
const inheritedAttributes = pick( firstCellInColumn, INHERITED_COLUMN_ATTRIBUTES ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is alignment inherited?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When adding a new row, the newly added cells will keep the same alignment as the rest of their adjacent column.
As already mentioned elsewhere in this PR, it seems like all cells in a column should have the same alignment, so this maintains that experience for the user. In my testing it felt like the right way to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
rowIndex, | ||
columnIndex, | ||
content, | ||
attributeName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would read better if attributeName
is a separate argument. E.g. getCellAttribute( state, 'align', cell )
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed it could be improved. I've changed it to getCellAttribute( state, cell, 'align' )
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! It seems like a good idea to keep cellLocation
as a separate entity. Could this object be documented somewhere to the type can be used in the docs of all these functions?
setAttributes( updateSelectedCell( | ||
attributes, | ||
selectedCell, | ||
( cellAttributes ) => ( { ...cellAttributes, content } ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need a callback here? Can this not be done directly by updateSelectedCell
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be overkill, but I feel it's a more useful API. Being able to read the cell's attributes provides an opportunity for doing things like toggling values or incrementing/decrementing values when updating them.
I assume one can then multi select cells in a column and have all of them align in one go. |
72a11ec
to
1dc0a87
Compare
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
Add jsdocs Inherit the alignment property of the first cell in the column when adding new rows Update e2e tests Use consistent letter casing
Update params of getCellAttribute function Do nothing if insertRows is unable to determine the correct cellCount for the insertion
1dc0a87
to
f00dd4e
Compare
@paaljoachim Sort of, the table still only has cell-based selection, but alignments apply to the column that the selected cell is in. |
*/ | ||
async function changeCellAlignment( align ) { | ||
await clickBlockToolbarButton( 'Change column alignment' ); | ||
const alignButton = await page.$x( `//button[text()='Align Column ${ capitalize( align ) }']` ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the clickButton
utility function be used here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Genuinely didn't know that existed. Will do a follow up PR to tidy up the tests.
await page.keyboard.type( '4' ); | ||
|
||
// Create the table. | ||
const [ createButton ] = await page.$x( createButtonSelector ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here, could clickButton
be used?
* 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
* 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
Description
Adds column alignment options to the table block. Closes #15823
Dev decisions
Code-wise, the part where I've had to break from the norm is by using a
data-align
attribute to store the alignment value for a cell. This is because the cell attributes are deeply nested and sourced using aquery
matcher, so normal attributes can't be used.Applying an alignment adds a class name and
data-align
attribute to each table cell in a column. There aren't many alternatives to this approach that I can think of, apart from using inline styles.colgroup
/col
elements don't support text align, so they're out of the question.How has this been tested?
Added unit and e2e tests.
Manual testing steps:
Screenshots
Editor
Post
Types of changes
New feature (non-breaking change which adds functionality)
Checklist: