-
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(Tab): Add component #430
Conversation
I just implemented a simple tab-based component in our app. Keep track of the selected tab by it's array index so you can easily determine which tab should be active and which pane should be visible. The paneProps could either be an const tabs = [
{
...tabProps,
pane: {
...paneProps
}
}
]
<Tab tabs={tabs} />
// Would render:
<Tab.Menu>
{tabs.map(tab, (props, index) => <Tab.Menu.Item {...props} active={index === this.state.selectedIndex} /> */}
</Tab.Menu>
{<Tab.Pane {...tabs[this.state.selectedIndex].paneProps} /> */} |
Thanks for the feedback. Did you define your children in const tabs = [
{
...tabProps,
pane: {
children: (
<div>
<Message success icon='smile' content='Welcome to Tab #1!' />
<p>
This is the content of the main
</p>
<Form>
<Field control='input' label='First Name' />
<Field control='input' label='Last Name' />
<Field.Checkbox label='I agree to the terms and conditions' />
<Field.Button type='submit'>
Sign Up
</Field.Button>
</Form>
</div>
),
}
}
] You could use an array there as well if you wanted children as siblings. I just used an expression. |
We haven't gotten that far and currently it's somewhat coupled to our use-case. Basically the Edit: To clarify, our current implementation is actually more like: <Menu>
{tabs.map(tab, (props, index) => <Menu.Item {...props} active={index === this.state.selectedIndex} /> */}
</Menu>
{<Segment>{ tabs[this.state.selectedIndex].paneProps.renderer()}</Segment> */} I think this should definitely take advantage of the fact that the const tabs = [
{
...tabProps,
pane: {
...paneProps
}
}
]
<Tab tabs={tabs} />
// Would render:
<Menu>
{tabs.map(tab, (props, index) => <Menu.Item {...props} active={index === this.state.selectedIndex} /> */}
</Menu>
{<Segment {...tabs[this.state.selectedIndex].paneProps} /> */} Personally we have a use-case for tab functionality but without the default attached/tabular style. I'd love to not have to maintain my own. I could see being able to pass menu props and segment props that would override the default (tabular/attached). Or perhaps it would be better for the default to NOT have the tabular/attached styles applied and this component without customization would just provide tab functionality. It would make it easier to style custom and wouldn't really require much overhead for developers who want to use the tabular/attached menu styles. <Tab tabs={tabs} attached='top' tabular />
// OR
<Tab tabs={tabs} menuProps={{attached:'top', tabular:true}} /> |
Another good React tab implementation for reference http://zippyui.com/docs/react-tab-panel |
Thanks for the link, I had checked out these and a few others, but not react-tab-panel: https://github.com/reactjs/react-tabs The common thread seems to be that ever child is a tab. It defines the tab name via a prop, and content via its children. I like the idea of passing the array in though. Not sure where this will go yet. |
I think passing the tab specific props to a tab like icon, label, if it is closeable, etc., However props that controls overall tabs should be able to specify at parent like if tabs should appear left or right or if any can be closed etc., An Example.
|
I've pleasantly realized that only <Tab as={Segment} /> This way the user can use any tab component. My use case simply required a |
@levithomason - Would you mind if I took this PR off your hands? We're gonna need this soon. Based on recent work completed, I'm thinking: <TabGroup>
<Tab menuItem='Tab 1' as={Segment}>Tab 1 content</Tab>
<Tab menuItem='Tab 2' as={Segment}>Tab 2 content</Tab>
<Tab menuItem='Tab 3' as={Segment}>Tab 3 content</Tab>
<TabGroup>
// Would generate:
<Menu>
<Menu.Item content='Tab 1' />
<Menu.Item content='Tab 2' />
<Menu.Item content='Tab 3' />
</Menu>
<Segment /* Tab 1 */>Tab 1 content</Segment>
<Segment /* Tab 2 */>Tab 2 content</Segment>
<Segment /* Tab 3 */>Tab 3 content</Segment>
One hangup, this is going to have to be wrapped in a |
Sure thing, I'll provide review where I can. Head it up how it makes sense for you. My only guideline is that is support all valid SUI core usages and that the props parity the SUI names. |
7c9654c
to
fb25a5a
Compare
@levithomason - took a first pass at this, came out pretty well I think: Generated HTML: <div>
<div class="ui top attached tabular menu">
<a class="item">Tab 1</a>
<a class="active item">Tab 2</a>
<a class="item">Tab 3</a>
</div>
<div class="ui bottom attached ui tab segment">Tab 1 Content</div>
<div class="ui bottom attached ui active tab segment">Tab 2 Content</div>
<div class="ui bottom attached ui tab segment">Tab 3 Content</div>
</div> Some notes/thoughts:
I'm using the
|
Does it makes sense to name it @jcarbo thank you for taking this up. |
fb25a5a
to
f72d06a
Compare
Codecov Report
@@ Coverage Diff @@
## master #430 +/- ##
==========================================
+ Coverage 99.75% 99.75% +<.01%
==========================================
Files 142 144 +2
Lines 2441 2493 +52
==========================================
+ Hits 2435 2487 +52
Misses 6 6
Continue to review full report at Codecov.
|
Re:
I would 100% agree with <div class='ui tabs'>
<div class='ui tab'></div>
<div class='ui tab'></div>
<div class='ui tab'></div>
</div> but the I wound up addressing the points related to the
The update to the factory is at ae70c05 and usage can be see at fdf38cd |
Thinking about how to make this a one-liner configured by data... // This
<Tab menu={{ attached: 'top', tabular: true }}>
<Tab.Segment as={Segment} attached='bottom' menuItem='Tab 1'>Tab 1 Content</Tab.Segment>
<Tab.Segment as={Segment} attached='bottom' menuItem='Tab 2'>Tab 2 Content</Tab.Segment>
<Tab.Segment as={Segment} attached='bottom' menuItem='Tab 3'>Tab 3 Content</Tab.Segment>
</Tab>
// Could be written as:
const items = [
menuItem: 'Tab 1', as: Segment, attached: 'bottom', content: 'Tab 1 Content',
menuItem: 'Tab 2', as: Segment, attached: 'bottom', content: 'Tab 2 Content',
menuItem: 'Tab 3', as: Segment, attached: 'bottom', content: 'Tab 3 Content',
]
<Tab menu={{ attached: 'top', tabular: true }} items={items} /> The shorthand factory makes this abstraction really easy. I could even see only supporting the latter (data-driven approach) since the former relies on dealing with Edit In fact, thinking about it more, the shorthand factory takes elements so this would be equally valid: const items = [
<Tab.Segment as={Segment} attached='bottom' menuItem='Tab 1'>Tab 1 Content</Tab.Segment>,
<Tab.Segment as={Segment} attached='bottom' menuItem='Tab 2'>Tab 2 Content</Tab.Segment>,
<Tab.Segment as={Segment} attached='bottom' menuItem='Tab 3'>Tab 3 Content</Tab.Segment>,
]
<Tab menu={{ attached: 'top', tabular: true }} items={items} /> I think this may be the best approach. |
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.
Naming
Agreed. Let's keep top level API parity in tact. We also strive to keep the sub component API 1:1 with SUI component parts.
Since the only component part is a tab
(.ui.tab > .tab.active
) we would technically do Tab.Tab
. IMO, this is very unintuitive and we should pick something else. I propose we go with Tab.Pane
since there is technically no required .ui.tab > .segment
component part either. It is just that SUI uses that as the default because it can be bottom attached
to the tabular menu
easily.
Docs
It seems there is an issue with the loading example, the loading Segment is always visible:
Data Driven & Variations
I really like the data driven approach. It makes the component logic so much cleaner. It also is inline with our shorthand props / sub component API pattern.
Per the tabular menu variations, there are some valid uses cases I can't see how to cover with the current API.
The 3rd tabular example includes a right
sub menu. If we used menu
shorthand I think we should also expose a Tab.Menu
component. This would default to as: Menu
. Then the user can define any JSX needed for more complicated menus. Not sure of a good way to organize this since there are no child elements in the SUI markup to work with. Perhaps this?:
<Tab>
<Tab.Menu />
<Tab.Pane />
<Tab.Pane />
</Tab>
The 4th and 5th tabular menu examples show a Menu and Segment that exist in separate places, Grid Columns. I'm out of time to explore this more ATM, but one crazy idea comes to mind for allow this somehow:
const { TabMenu, TabPanes } = Tab.create(menu, tabs)
I'm not even sure how this would work, just tossing ideas out there for now.
src/modules/Tab/Tab.js
Outdated
|
||
import TabSegment from './TabSegment' | ||
|
||
class Tab extends Component { |
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.
Let's add the SUI tab description as a doc block above the Tab here. This gives the page level decription in the docs.
Interesting high level perspective here, there is no SUI tab component actually. There is only css to show and hide the active tab. The menu and tab themselves have no visual styles. All the styling is handled by the component you use as a menu and as a tab (ie Menu, Segment). Since React can show and hide content easily, we're not actually limited at all by the SUI tab classes nor markup. We could actually deviate 100% to make the most sane React tabs and not lose anything. The only thing to lose AFAICT is the visibility CSS associated with the ".tab.active" className, which we could care less about if we choose. Food for thought. |
@jcarbo did you end up with a final solution here in your own apps? |
We wound up with something similar to what was described above:
While there's no class for the tab group wrapper, there's a tab class that gets applied to the content (e.g. segment) which has two modifying classes:
So there's at least one visual style that this component would provide, along with the basic tab functionality. I'll try to clean this PR up a little and see where we're at. |
One thing I'd like to mention is "async tabs" where you can pass a callback to the tab panes to fetch it's children. In our application there's a page we need that has 4 tables tabbed out. Each table is large and we'd like to not render it unless that tab is active. Just a thought. |
Available before Xmas? =] |
Cutting it close! |
67d6ca9
to
f76c035
Compare
I don't mean to be annoying, but would just really like to be able to use the tab functionality and was wondering if there is a planned eta to make this available in the latest npm build? |
No worries, I don't take checkups to be annoying :) The ETA is "as soon as possible", though, this module isn't required by any paid work so it is certainly deprioritized. Completion wise, I believe I'm well over 90% here. Just needs to more tests and cleanup. Any help appreciated. You can PR against this branch to help out. Just run |
f76c035
to
b281663
Compare
b281663
to
39c928b
Compare
This looks cool and almost ready. Is there any chance this will get the last few fixes so it can go into master? |
There is indeed a really good chance. It has been on the bucket list for quite some time and has gotten to my nerves. |
If you need some help, I don't mind to take a look.
Edit: I took a look at this. After the last commit (39c928b) it seems like a lot has changed. I have seen that;
It does seem like an interesting task but I am not sure as a n00b to the project it is a good idea. :) |
@jseminck thanks for the deep dive into the state of things. I'm mid-way through changing the API. Perhaps after I solidify the component and the initial example there will be room for a handoff or pair effort here. Very much appreciated. |
74f9c7d
to
79773d9
Compare
@jeffcarbs @brsanthu @UnbrandedTech @AlvMF1 @dpkwhan @mclarentgp @dfsp-spirit @jseminck This component is ready for some user testing and feedback. How?If you could pull this branch FYII went with I do think there is room for a single top level Let me know your thoughts! |
@levithomason Awesome work 👍 I've added initial typings and some style fixes. However, I don't think that |
@levithomason Looks great, awesome work! I was curious though since all tab examples are horizontal, if it was possible to do a vertical set up with the tabs being on right or left side of the segment? I tried to get an example working vertically, but unfortunately wasn't able to do so. |
render()@layershifter My thought here was based on @UnbrandedTech's #430 (comment):
The render method then allows you define tab content that will not be rendered until that tab is made active. I think in this case it may be an acceptable departure from the pattern found in, say the Accordion, where all panes are rendered on initialization. Let me know your thoughts. Vertical@mclarentgp I initially had a smarter Tab that managed the Menu and Segment with more features, but removed it in order to get this out the door. I would love to support vertical tabs immediately following this PR, hopefully a community contribution. The thing with vertical tabs is that the Menu and Segment need to be separated by a Grid with |
a819da3
to
bae3aaf
Compare
Added remaining docs. Just need to finish Tab test updates here and we're good to go. |
@levithomason I did this in the tab.js file and imported the Grid and GridColumn controls into the file ` renderColumn(columnProps) { renderVertical(menu) {
} render() {
}` added the following to GridColumn.js and then modified one of the existing examples to test this out. const panes = [ const TabExampleAttachedBottom = () => ( export default TabExampleAttachedBottom` What are your thoughts? Is this even a valid/good way of achieving vertical tabs? |
Indeed, this is the general direction I think it could go. I initially had a similar solution, importing the Grid and wrapping the Menu / Segment when there was a |
bae3aaf
to
e8683e1
Compare
e8683e1
to
f689f91
Compare
It is done! |
Released in |
Fixes #199. This PR adds the Tab component. There are a number of ways we could go with the API. I'll post an API proposal soon.
Considering a Tab component is simply a
tabular menu
followed by sometab segment
s, I think we may create very a simple Tab API. More complicated use of Tabs could use an actual Menu and Segments with manual wiring of the click handlers andactive
class.Simple Tabs
Custom Tabs
This type of customization requires a full definition of the Menu component (icons, right sub menu, etc). I think at this point, the user should just wire in their click handlers to render tab Segments manually. An API for doing this will make the simple use case obtuse.
Open to suggestions on how to accomplish both with a clean and concise API.
Considerations
This component doesn't lend itself well to an array shorthand prop such as
panes
since the content is bound to be much more than just text. Because of this children will be used often. It is difficult and unreliable to loop/filter children to add click handlers and props for auto controlling things like theactive
Segment (pane). We are also avoiding React context to do so if at all possible.Given these points, I'm leaning toward something like the simple tabs markup shown above. Will come back to this later. Feedback is much welcomed.