-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
feat(Table): add table row shorthand #567
Conversation
Current coverage is 99.62% (diff: 100%)@@ master #567 diff @@
==========================================
Files 118 118
Lines 1862 1874 +12
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 1855 1867 +12
Misses 7 7
Partials 0 0
|
This looks really great, thanks. I'll try to focus on a review tomorrow. |
a4eb81c
to
6a94bf3
Compare
Added specs for the shorthand rendering that should bump the coverage back to 100%. |
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.
Awesome, thanks so much for starting this. Let's iterate, it's going in a great direction. Shorthand is really paying off!
itemAs: customPropTypes.as, | ||
|
||
/** Shorthand array of props for TableCell. Mutually exclusive with children. */ | ||
items: customPropTypes.every([ |
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.
How about going with cells
instead of items
?
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.
Bump
import { Table } from 'stardust' | ||
|
||
const headerRow = [ | ||
'Name', |
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 each row renderer should get access to the row element. How about going with the same pattern for the header/footer as the table body renderer?
const headerRow = (firstRow) => ({
items: [
'Name',
'Status',
'Notes',
],
})
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.
The reason the body row renderer needs to be a function is that each row in the body is a function of an element in the tableData
array. The header and footer rows are static as far as the Table
is concerned so I'm not sure if it would make sense for them to be functions.
I think each row renderer should get access to the row element.
Also to clarify, headerRow
is the row element, same with footerRow
. bodyRow
is a more of a "template" of what a row element looks like.
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.
Consistency
I see that logic and it is valid thinking. What I'm mostly after here is consistency, where all "row" props are the same type / signature and behave the same. I think this is important for users. Especially given the props have the same naming convention, it might not be clear that they are doing different things.
I'm also assuming that we'll eventually have add column shorthand with cell renderers, which would again be functions with similar signatures. Having all table shorthand always be a function that receives some data and returns something I think is good.
Power
What I'm less after, is just the fact that functions are more powerful and flexible than configuration due their reusable and composable nature. Contrived example, I can imagine dynamic reusable header functions that do things like "alphabetically sorted", etc. Without a function, the user would have to manually sort the table data outside of the table and generate sorted headers every time. Whereas with a header row function, you can make a single reusable alphabeticallySortedHeader
function.
I often find myself refactoring to add a function where configuration was provided so it can have more control / ability. Though, I can't say I've ever had to refactor a function down to configuration for any reason.
All said, I want to keep flexible. LMK if you still don't agree. I'd settle for a simple prop name update to help differentiate the renderer function from the static props:
<Table headerRow={} renderBodyRow={} footerRow={} />
👍
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.
+1 for the rename of bodyRow
=> renderBodyRow
to differentiate it more. Not completely opposed to making the other two functions in the future, but for now I'd prefer keeping it as-is. Perhaps when we add column/cell shorthands it will give a little more clarity.
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'm on board 👍
@@ -46,7 +46,7 @@ export function createShorthand(Component, mapValueToProps, val, defaultProps = | |||
} else if (_.isPlainObject(val)) { | |||
type = 'props' | |||
usersProps = val | |||
} else if (_.isString(val) || _.isNumber(val)) { | |||
} else if (_.isString(val) || _.isNumber(val) || _.isArray(val)) { |
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.
(...submitting a little half baked, ran out of time on this comment but want to get the convo started)
I'm not sure we should pass arrays on to be mapped to props. It means every component might do something different with the array (or not handle it at all). If the factory does handle arrays, IMHO it is most intuitive to expect it to always create an array of elements.
const button = Button.create('Submit')
// `key` derived from the string/number literal or `childKey` prop
const buttons = Button.create(['A', 123])
const buttons = Button.create([{ content: 'A' }, { content: 123 }])
const buttons = Button.create([<Other>A</Other>, <Other>123</Other>])
Then we can standardize shorthand propTypes since all components with shorthand would be capable of handling arrays.
If the factory passes it along, we'd need to make sure every mapValueToProps
handles arrays. Though, every component then might do something different with the array which isn't clear to the user. "How does X handle arrays again?" Also, not all components / mapValueToProps()
may be able support arrays. It could make standard propTypes tough.
Lastly, when passing an array, you are creating an array of X components. If there is a need to pass that array through the X's props to some Y's shorthand, then the first component is just a pass through. My gut says there should be a "one level deep" design decision. Meaning, we would forgo the middle man and pass the shorthand directly to the Y instead of through X to Y.
...totally open on this, just thinking out loud.
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.
To give a little more context, I came up with these shorthands by building upwards and trying to collapse where possible. So:
// This
<Table.Cell>Name</Table.Cell>
// became
<Table.Cell content='Name' />
// This
<Table.Row>
<Table.Cell content='Name' />
<Table.Cell content='Status' />
<Table.Cell content='Company' />
</Table.Row>
// became
<Table.Row cells=['Name', 'Status', 'Company'] />
// This
<Table>
<Table.Header>
<Table.Row cells=['Name', 'Status', 'Company'] />
</Table.Header>
</Table>
// became
<Table headerRow={{ cells: ['Name', 'Status', 'Company'] }} />
If you think about our shorthand, generally it lets you pass a literal in place of a one-key object, e.g.
// One-key object
<Label detail={{ content: 'Some detail' }} />
// Literal
<Label detail={'Some detail'} />
So I figured if we can describe a Table.Row as a one-key object, we should be able to describe it as a "literal":
// One-key object
<Table headerRow={{ cells: ['Name', 'Status', 'Company'] }} />
// Literal
<Table headerRow={['Name', 'Status', 'Company']} />
So I'm not thinking about using arrays to let you build an array of some component (e.g. const buttons = Button.create(['A', 123])
), but instead I'm thinking about it as a valid way of describing a component that has a list of things (e.g. TableRow
has a list of cells that can completely describe that row).
Other examples:
// One-key object
Menu.create({ items: ['menu item 1', 'menu item 2', 'menu item 3'] })
// Literal
Menu.create(['menu item 1', 'menu item 2', 'menu item 3'])
// One-key object
List.create({ items: [<List.Item />, { content: 'item 2', description: 'item 2'}, 'list item 3'] })
// Literal
List.create([<List.Item />, { content: 'item 2', description: 'item 2'}, 'list item 3'])
Thoughts?
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 had a sudden moment of clarity thinking about this haha. There are really two separate types of shorthands we're talking about:
What is shorthand?
A way of describing component using a literal value, a props object, or an complete React.Element
Item Shorthand
What we've dealt with thus far. Describes an individual component that may or may not have children. If it does have children, the children tend to be heterogeneous/different (e.g. a label can have multiple children but they're all different: icon, image, content, detail, delete-button)
Valid propTypes:
- string/number
- Object (props)
- Element
Example Components
- Label
- Icon
- TableCell
Collection Shorthand
What this PR is introducing. Describes an individual component that has a set of homogenous children (e.g. a menu has 1+ items that are all the same).
Valid propTypes:
- array (of "item shorthand"s for set of children)
- Object (props)
- Element
Examples
- Menu
- List
- TableRow
I think almost everything is the same for both (e.g. they can use the same factory function). The differences are:
- Which validations we apply for the "literal" case. Could just have
customPropTypes.collectionShorthand
andcustomPropTypes.itemShorthand
(if we like these names). - How the
mapValueToProps
function maps a literal, but that's defined within the individual component itself so there shouldn't really be an issue there.
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.
Cool, I think we're seeing the same split in types of shorthand. This is what I ran into here where I reference customPropTypes.shorthandSingle
and customPropTypes.shorthandMultiple
. I like collection a lot better than multiple though 😄.
So, I think we have 2 confirmed types of shorthand then. Both handle a props objects and elements identically. The difference is in what other values they accept and how they mapValueToProps
:
- Single item factory (Icon, etc.) also handles string/number mapping them to props.
- Collection factory (Menu, etc.) also handles arrays mapping them to a single collection prop (items, cells, etc).
There is a 3rd group we can deal with later, since I think they need API redesign. Breadcrumb, Accordion, and possibly Feed, which take an array but do not map it to props. Instead, they create children in a special way, usually due to markup constraints. We'll save that for another day and time.
I'm working on the Feed updates and shorthand propTypes as I find time. Won't hold up this PR for that work. I can circle back and update when my work there is ready.
@@ -76,6 +93,20 @@ TableRow.propTypes = { | |||
/** A row may call attention to an error or a negative value. */ | |||
error: PropTypes.bool, | |||
|
|||
/** An element type to render as (string or function). */ | |||
itemAs: customPropTypes.as, |
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.
Not sure of/if there is a solution yet, but I'd love to remove this prop somehow. Seems an indicator of a larger pattern problem. We had this on the Label as well, but were able to remove it with sub components.
One idea would be that the implementing component would pass the as
prop / key in each item in the array so it is explicit. If that is too cumbersome, perhaps we need TableHeaderRow
or something, similar to the two Table cells.
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.
The Label
had detail
and detailAs
which we merged into one:
<Label detail={{ content: 'x', as: 'div' }} />
This is a little more tricky since items
is an array of shorthands so it wouldn't be straightforward to add an "as" prop to each one since they may all be literals. For reference, this it where it would have to be done:
{headerRow && <TableHeader>{TableRow.create(headerRow, { itemAs: 'th' })}</TableHeader>}
In this case, headerRow
is a shorthand for a TableRow
.
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.
OK, let's save it for a later PR.
Here's an idea for the record. I have this same issue with FeedExtra
. It has the need for two different shorthands, one that uses images
and one that uses text
. I'm thinking of just adding:
FeedExtra.createImages()
FeedExtra.createText()
What if we added this?
TableRow.create()
TableRow.createHeader()
The latter would set as
on each element in the array. I think this touches on how collection shorthands are different than an item shorthand. A string or number is not valid in a collection, TableRow.create('not valid') //=> <Table items='not valid' />
. So it seems we don't need a factory that only provides a mapValueToProps
but instead/also a way to update each item. Perhaps with a default props object/function somehow. Again, this comment is for later reference.
|
||
const TableWarningShorthand = () => { | ||
return ( | ||
<Table celled headerRow={headerRow} bodyRow={bodyRow} tableData={tableData} /> |
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.
API suggestion:
<Table
renderHeaderRow={...}
renderBodyRow={...}
renderFooterRow={...}
/>
Eventually, I'd like to also add column/cell shorthand. These would disallow use of the row shorthand, but I think it would give the user the ability to map through their data in what ever way made the most sense to them.
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.
Per more recent convo, ignore ^
import { Table } from 'stardust' | ||
|
||
const headerRow = [ | ||
'Name', |
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.
Consistency
I see that logic and it is valid thinking. What I'm mostly after here is consistency, where all "row" props are the same type / signature and behave the same. I think this is important for users. Especially given the props have the same naming convention, it might not be clear that they are doing different things.
I'm also assuming that we'll eventually have add column shorthand with cell renderers, which would again be functions with similar signatures. Having all table shorthand always be a function that receives some data and returns something I think is good.
Power
What I'm less after, is just the fact that functions are more powerful and flexible than configuration due their reusable and composable nature. Contrived example, I can imagine dynamic reusable header functions that do things like "alphabetically sorted", etc. Without a function, the user would have to manually sort the table data outside of the table and generate sorted headers every time. Whereas with a header row function, you can make a single reusable alphabeticallySortedHeader
function.
I often find myself refactoring to add a function where configuration was provided so it can have more control / ability. Though, I can't say I've ever had to refactor a function down to configuration for any reason.
All said, I want to keep flexible. LMK if you still don't agree. I'd settle for a simple prop name update to help differentiate the renderer function from the static props:
<Table headerRow={} renderBodyRow={} footerRow={} />
👍
|
||
const TableWarningShorthand = () => { | ||
return ( | ||
<Table celled headerRow={headerRow} bodyRow={bodyRow} tableData={tableData} /> |
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.
Per more recent convo, ignore ^
itemAs: customPropTypes.as, | ||
|
||
/** Shorthand array of props for TableCell. Mutually exclusive with children. */ | ||
items: customPropTypes.every([ |
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.
Bump
@@ -76,6 +93,20 @@ TableRow.propTypes = { | |||
/** A row may call attention to an error or a negative value. */ | |||
error: PropTypes.bool, | |||
|
|||
/** An element type to render as (string or function). */ | |||
itemAs: customPropTypes.as, |
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.
OK, let's save it for a later PR.
Here's an idea for the record. I have this same issue with FeedExtra
. It has the need for two different shorthands, one that uses images
and one that uses text
. I'm thinking of just adding:
FeedExtra.createImages()
FeedExtra.createText()
What if we added this?
TableRow.create()
TableRow.createHeader()
The latter would set as
on each element in the array. I think this touches on how collection shorthands are different than an item shorthand. A string or number is not valid in a collection, TableRow.create('not valid') //=> <Table items='not valid' />
. So it seems we don't need a factory that only provides a mapValueToProps
but instead/also a way to update each item. Perhaps with a default props object/function somehow. Again, this comment is for later reference.
/** Shorthand array of props for TableCell. Mutually exclusive with children. */ | ||
items: customPropTypes.every([ | ||
customPropTypes.disallow(['children']), | ||
// Array of shorthands for MenuItem |
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.
MenuItem
=> TableRow
, or just pull this comment entirely.
Ugh, still getting used to the new way GH groups/shows/hides review comments. LMK if that last review is not clear. I did it on the files tab where I could see my review comments next to the past comments. |
Just ran into the need to pass array values to |
* TableCell shorthand * TableRow shorthand * Table shorthand * Add example to test table shorthand
- TableRow: items => cells - Table: bodyRow => renderBodyRow
6a94bf3
to
022dc8f
Compare
Made the requested changes, rebased and pushed up the latest. There are some further enhancements that I think can be addressed on subsequent PRs, so I think this is ready for final review 👍 |
Just a question.. So populating my table with data from my props, I should use the short hand method as shown here as opposed to Thanks. |
Both are valid, whatever works best for you and your app 👍 http://react.semantic-ui.com/collections/table#warning-shorthand |
Fixes #565
Add shorthand for Table and subcomponents:
I wound up implementing almost exactly like I had laid out in this comment: https://github.com/TechnologyAdvice/stardust/issues/565#issuecomment-250344807
I really like the way this came out. I think it allows you to be incredibly succinct in the simplest case but give you full control over the entire table (save for the
thead
,tbody
,tfoot
) elements. I think this is a good example of how powerful these shorthand factories are.To give some more examples, assume this data:
The most basic table would be:
Now if one of those cells needs customizing, you can change that cell to any valid
TableCell
shorthand:In these examples, the array of cells is just a shorthand for
TableRow
, so you can returnTableRow
props or a your own custom component: