Skip to content
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(tabs): Implementation #138

Merged
merged 26 commits into from
Mar 19, 2020
Merged

feat(tabs): Implementation #138

merged 26 commits into from
Mar 19, 2020

Conversation

Vignesh-Manogar-E3433
Copy link
Contributor

Description

Adding 'Tabs' component to Nucleus.

Tabs are used to organise content under each section. Tabs are most helpful when there is a lot of content to show in a page. Tabs can help in showing content which are under the same level of hierarchy, under each section inside the same page.

Related Bug

@Vignesh-Manogar-E3433 Vignesh-Manogar-E3433 marked this pull request as ready for review March 6, 2020 12:16
@shibulijack-fd shibulijack-fd changed the title feat(tabs): Initial commit feat(tabs): Implementation Mar 9, 2020
@shibulijack-fd shibulijack-fd added the enhancement New feature or request label Mar 9, 2020
@netlify
Copy link

netlify bot commented Mar 9, 2020

Deploy preview for freshworks-nucleus processing.

Building with commit dba6afd

https://app.netlify.com/sites/freshworks-nucleus/deploys/5e73781d068d3a0007d29b35

* @type boolean
* @public
*/
@computed('props.[]', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of computing the values inside props, we could directly pass the prop elements from the parent component and consume accordingly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Now directly passing the values.


{{yield (hash
panel=(component "nucleus-tabs/tab-panel")
props=(hash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I feel it's a little strong to be called anti-pattern (as some Ember devs would), I would say it's not the Ember convention to send props like this. Also, this implies that you have to watch for any changes in the props hash even to listen for one of the values which could have performance implications.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. Changed as suggested.

@extends Ember.Component
@private
*/
@tagName('button')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use {{nucleus-button}}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tab and Button have completely different aria attributes, roles. Styling and behaviour also differs. Hence just changed the base tag as per our discussion to avoid any confusions.

*/
init() {
super.init(...arguments);
once(this, get(this, 'registerTabListItem'), {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a run loop necessary here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed using run loops. Was able to achieve the same result by using lifecycle event. Updated.

border-radius: 4px;
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please refactor this file using BEM?
https://css-tricks.com/bem-101/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was already following BEM naming conventions. Have changed a couple of lines to be more compliant.

{{/nucleus-tabs}}
`);
await backstop(assert, {scenario:{misMatchThreshold: 0.1}});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Visual regression tests are expensive, i.e they take a long time to run and taking snapshots are quite costly too. So try to render multiple variations in one test as much as possible.

Copy link
Contributor Author

@Vignesh-Manogar-E3433 Vignesh-Manogar-E3433 Mar 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged visual regression test cases into one as suggested.

Merging cases into one also led to reducing backstop 'misMatchThreshold'. Something to be wary of next time. Else, cases were passing when they should have failed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging cases into one also led to reducing backstop 'misMatchThreshold'. Something to be wary of next time.

I have already brought this up in an issue - #143
We are currently letting the developer be the better judge of the misMatchThreshold depending on the way they have written the test. It is not necessary that all visual regression tests should have the same misMatchThreshold. We will have to revisit the already existing components too and change threshold values if required

@Vignesh-Manogar-E3433 Vignesh-Manogar-E3433 self-assigned this Mar 18, 2020
* @private
*
*/
_focusPreviousTab(element, elementInFocus) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is almost the same as _focusNextTab. So can you refactor this into a single function with an additional param to address both the use cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Their primary functionality is the same, but they simply operate on different nodes. Combining them makes more mess than solve something.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_focusTab(element, elementInFocus, direction) {
    let siblingElement;
    if(direction) {
        siblingElement  = (element.nextElementSibling)? element.nextElementSibling : element.parentElement.firstElementChild;
    } else {
      siblingElement = (element.previousElementSibling)? element.previousElementSibling : element.parentElement.lastElementChild;
}
   
    if(elementInFocus && (elementInFocus.id === siblingElement.id)) {
      return;
    } else if(siblingElement.getAttribute('tabindex') === null) {
      get(this, '_focusTab').call(this, siblingElement, element, direction);
    } else {
      siblingElement.focus();
    }
  }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D.R.Y: Don't Repeat Yourself.

Having two functions with almost the same lines of code is a prime candidate to apply this principle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@classNameBindings('isActive:is-active')
@attributeBindings('tabindex')
@attributeBindings('role')
@attributeBindings('aria-labelledby')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@attributeBindings accepts a list of values. So this can be refactored to a single line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

*
*/
init() {
super.init(...arguments);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this lifecycle hook can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

@attributeBindings('aria-controls')
@attributeBindings('aria-selected')
@attributeBindings('isDisabled:disabled')
@attributeBindings('title')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor to single binding calls.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

*/
@action
async activateTab(changedTo, event) {
const _this = this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? I don't see any scope changes within this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

const _this = this;
const beforeChange = get(_this, 'beforeChange');
const onChange = get(_this, 'onChange');
const currentTab = get(_this, 'selected');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use getProperties to batch the getter jobs.

https://api.emberjs.com/ember/release/functions/@ember%2Fobject/getProperties

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

*
*/
mouseDown() {
if(get(this, 'isDisabled') === false) set(this, 'isPressed', true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there's a linter rule for this, but can you wrap it in { }

mouseDown() {
    if(get(this, 'isDisabled') === false) {
       set(this, 'isPressed', true);
    }
}

P.S: It's just a convention that we follow in our codebase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will follow this hereon.

* @private
*
*/
_focusPreviousTab(element, elementInFocus) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_focusTab(element, elementInFocus, direction) {
    let siblingElement;
    if(direction) {
        siblingElement  = (element.nextElementSibling)? element.nextElementSibling : element.parentElement.firstElementChild;
    } else {
      siblingElement = (element.previousElementSibling)? element.previousElementSibling : element.parentElement.lastElementChild;
}
   
    if(elementInFocus && (elementInFocus.id === siblingElement.id)) {
      return;
    } else if(siblingElement.getAttribute('tabindex') === null) {
      get(this, '_focusTab').call(this, siblingElement, element, direction);
    } else {
      siblingElement.focus();
    }
  }

* @private
*
*/
_focusPreviousTab(element, elementInFocus) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D.R.Y: Don't Repeat Yourself.

Having two functions with almost the same lines of code is a prime candidate to apply this principle.

@shibulijack-fd shibulijack-fd merged commit 598e3e6 into master Mar 19, 2020
@shibulijack-fd shibulijack-fd deleted the feature/nucleus-tabs branch March 19, 2020 13:55
shibulijack-fd added a commit that referenced this pull request Mar 20, 2020
* master:
  fix(modal): fixes positioning of elements (#154)
  fix(toast-message): Design review updates  (#155)
  feat(tabs): Implementation (#138)
  chore(nucleus): Nucleus now lives in the Dark! (#152)
  fix(Inline-banner): refactored yield (#151)

# Conflicts:
#	packages/@nucleus/package.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants