Terra-core: Tab Component

Created on 4 May 2017  ·  27Comments  ·  Source: cerner/terra-core

Issue Description

Create tabs component.

Issue Type

  • [x] New Feature
  • [ ] Enhancement
  • [ ] Bug
  • [ ] Other

Expected Behavior

Needs expected behavior defined

Current Behavior

N/A

All 27 comments

Is there a timeline for Tabs to be added?

Tabs are something we are looking at for Q4. It largely depends on getting visuals from UX.

Tab Tech Design

Summary:

A tab container will have a panel of tabs with a content area to display tab content. When there is not enough room for all the tabs within the panel a drop down menu will become available with the tab items that could not fit. When an item in the menu is selected the menu text will be updated to contain the item's label. The menu toggle size will not change after initial render, updated text will be truncated if it does not fit.
Tabs will have a min and maxWidth set. When a tab is collapsed into the menu the label text will be used.
Consumers can create a controlled tab component with the use of the activeKey and onChange props. Otherwise the component will be uncontrolled with the option to set defaultActiveKey for initial tab selection.

Responsiveness:

When there is not enough room for all the tabs within the panel a drop down menu will become available with the tab items that could not fit.

Accessibility

Will follow rules outlined here for tabs: https://webaim.org/techniques/keyboard/

  1. Tab to navigate into and out of group of tabs
  2. Arrow keys will navigate between tabs
  3. Tab content will update automatically with arrow keys.

For a controlled tab component the arrow keys will trigger the onChange callback for the consumer to use to update the active tab.

Tab Container Props:

|Prop Name|Type|Is Required|Default Value|Description|
|---|---|---|---|---|
|children| node | required | none | Tab elements to be displayed |
| variant | string | required | "modular" | Tab style. One of: "modular", "structural" |
| onChange | func | optional | none | Callback function triggered on selection change. This will be called with the parameters: 1. event 2. the selected tab's key |
| activeKey | string | optional | none | Key of the tab that should be active. Use this prop along with onChange to create controlled tabs |
| defaultActiveKey | string | optional | none | Key of the tab that should be open initially |

Tab Props:

|Prop Name|Type|Is Required|Default Value|Description|
|---|---|---|---|---|
| icon | node | optional | none | Icon to be displayed on the tab. |
| label | string | required | none | Text to be displayed on the tab. |
| customDisplay | node | optional | none | A custom display for the tab. Component will fallback to label text when collapsed into the menu.|
| children | node | required | none | Content to display in content area when tab is open. |

Themeable Styles

TBD

Notes

A separate controlled tab component would no longer be needed with this updated approach.

+1

+1

Tab content will update automatically with arrow keys.

How do you envision this behavior to work when collapsed into the menu? Would it update the menu's text but keep it open for further navigation? Would arrowing to the menu trigger it to open and display it's contents or will enter need to be pressed first?

@emilyrohrbough I would allow the arrow keys to give focus to the menu toggle, but enter will need to be pressed to open it. Selecting an item in the menu will close the menu and then the menu toggle would have focus and arrows can be used to navigate back to the other tabs or enter could be pressed to open the menu again.

+1

Will be adding a separate tech design for a controlled tab component

Will the state in this tech design be controlled by the user or internally managed by the component? The word controlled is loaded

Controlled Tabs Tech Design

Summary:

The uncontrolled tabs will be built off of this base tabs component. This component will not manage the tab selection state but will manage the state of which tabs are face-up vs. collapsed as well as the open/close state of the menu. This component will also export helper functions for state management.

Responsiveness:

Same as uncontrolled component.

Accessibility

Similar to uncontrolled component. The arrow keys will trigger the onChange callback.

Selectable Tab Container Props:

|Prop Name|Type|Is Required|Default Value|Description|
|---|---|---|---|---|
|children| node | required | none | Tab element to be displayed |
| variant | string | required | "modular" | Tab style. One of: "modular", "structural" |
| onChange | func | optional | none | Callback function triggered on selection change. This will be called with the parameters: 1. event 2. the selected tab's key |

Themeable Styles

TBD

Notes

I'm unsure what to name this component. I put SelectableTabContainer here to align with the controlled list and table components, but I'm open to ideas.

Will Managed Tabs be a wrapper of Tabs?

Based on this description, this makes me think Tab Container should also accept an onChange which returns the selected tab, but does not handle state. Then Selectable Tab Container could be implemented like the ModalManger (or the concept of the SlidePanelManger) and this would be TabManger?

Or is Selectable Tab Container intended to account for a dynamic tab container?

The TabContainer will be a wrapper around the SelectableTabContainer, with the TabContainer handling selection state while the SelectableTabContainer does not. I didn't have an onChange on the TabContainer because for the vast majority of use cases consumers wouldn't really care which tab is being display after initial load. The SelectableTabContainer would allow the consumer to control the selection state allowing the consumer the ability to add/remove tabs if desired and be able to keep track of what tab was being shown if they needed to. I really don't like the name SelectableTabContainer for this component, but controlled vs. uncontrolled seems to be really confused terms for most people.

hmm okay I see now you've explained that. It might be helpful to add the part about TabContainer Wrapping Selectable Tab Container in the summary above so people can quickly gather the concept.

I definitely think the component naming will be important to distinguish the two. I'll try & think on some names. Atm nothing overly clear is coming to mind. 🤷‍♀️

+1 from me, my only question would be the name of the variant prop, I would maybe suggest using the name type but am indifferent either way.

@jmsv6d What are you thinking in terms of sample JSX for this component?

Based on the tech design, I'm guessing something like below?

<TabContainer variant="modular">
  <TabContainer.Tab label="Tab 1">First Tab</Tab>
  <TabContainer.Tabs isSelected label="Tab 2">Second Tab (selected by default)</Tab>
  <TabContainer.Tab label="Tab 3">Third Tab</Tab>
</TabContainer>

A design I've seen in other react tab implementations, (1, 2, 3), is using a defaultActiveKey prop and activeKey prop.

I'm thinking for the uncontrolled component, adding keys to the <Tab> components and replacing the isSelected prop on the <Tab/> component with a defaultActiveKey prop on the TabContainer.

<TabContainer variant="modular" defaultActiveKey="2">
  <TabContainer.Tab key="1" label="Tab 1">First Tab</Tab>
  <TabContainer.Tab key="2" label="Tab 2">Second Tab (selected by default)</Tab>
  <TabContainer.Tab key="3" label="Tab 3">Third Tab</Tab>
</TabContainer>

And then in terms of the controlled component, adding a prop named activeKey which would be used to control the component's active tab.

<SelectableTabContainer variant="modular" activeKey="2">
  <SelectableTabContainer.Tab key="1" label="Tab 1">First Tab</Tab>
  <SelectableTabContainer.Tab key="2" label="Tab 2">Second Tab (selected by default)</Tab>
  <SelectableTabContainer.Tab key="3" label="Tab 3">Third Tab</Tab>
</SelectableTabContainer>

I think we could keep the uncontrolled and controlled component 1 component, and flex between the two based of the presence of activeKey being set. This would work similar to what we do for form inputs, e.g. to make a terra form input a controlled input, users provide a value and an onChange prop to the <Input/> component. It would also be similar to how tabs in the reactjs org work.

The thought being, if activeKey is absent, the component works as an uncontrolled component and would handle switching between selected tabs as an implementation detail with ability to set the defaultActiveKey.

If activeKey is present, the component would use that to set selection state and as well as override/disable the behavior of defaultActiveKey.

I agree with the use of defaultActiveKey and activeKey props as the top level instead of the isSelected prop on the tab and having separate controlled and uncontrolled components. I think it will make the code a lot cleaner to use this approach. I will update and consolidate the tech designs unless anyone disagrees.

+1 for consolidating the component and flexing between controlled / uncontrolled based on the props as forms do

Thoughts on replacing TabContainer with Tabs?

<Tabs>
  <Tab />
  <Tab />
</Tabs>

@StephenEsser I like the simplicity of <Tabs><Tab /></Tabs> though it breaks the pattern we've been using of sub components using dot syntax.

Sticking with the dot syntax for children components, I could see using one of the following

// possible option 1
<Tabs>
  <Tabs.Tab>
</Tabs>

// possible option 2
<Tabs>
  <Tabs.Pane>
</Tabs>

// possible option 3
<Tabs>
  <Tabs.Panel>
</Tabs>

// possible option 4
<TabList>
  <TabList.Tab>
</TabList>

I agree with that, I had already aliased it in my head I should have called that out.

const Tab = Tabs.Tab;

My vote would be for option 2, but I would like to know other's opinions.

To me, conceptually option 1 makes the most since, though I could see this getting confusing. My second pick would be either option 2 or 3.

After meeting with Seth and Matt A. this afternoon to talk about designs. I saw a need for a few more props to be added.

Additional Tab Container Props

|Prop Name|Type|Is Required|Default Value|Description|
|---|---|---|---|---|
| tabFill | bool | optional | false | Indicates if the tab widths should expand to fill the space available, otherwise tab widths will fit to content |
| fill | bool | optional | false | Indicates if the container should expand to fill its parent element. |

Additional Tab Props

|Prop Name|Type|Is Required|Default Value|Description|
|---|---|---|---|---|
| isDisabled | bool | optional | false | Indicates if the tab should be disabled. |
| isIconOnly | bool | optional | false | Indicates if the tab should display icon only with the label used as fallback text if displayed in the menu and as a title within the content area when tab is active. |

+1 on tech design.

+1 looks good

JIRA created

Was this page helpful?
0 / 5 - 0 ratings

Related issues

noahbenham picture noahbenham  ·  4Comments

SpartaSixZero picture SpartaSixZero  ·  5Comments

StephenEsser picture StephenEsser  ·  5Comments

dv297 picture dv297  ·  4Comments

yuderekyu picture yuderekyu  ·  3Comments