-
Notifications
You must be signed in to change notification settings - Fork 11
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
Conversation
Basic functionality working Refractoring some code
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() { |
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.
Instead of computing the values inside props
, we could directly pass the prop elements from the parent component and consume accordingly
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.
Done. Now directly passing the values.
|
||
{{yield (hash | ||
panel=(component "nucleus-tabs/tab-panel") | ||
props=(hash |
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.
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.
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.
Yes, makes sense. Changed as suggested.
@extends Ember.Component | ||
@private | ||
*/ | ||
@tagName('button') |
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.
Please use {{nucleus-button}}
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.
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'), { |
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.
Is a run loop necessary 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.
Removed using run loops. Was able to achieve the same result by using lifecycle event. Updated.
border-radius: 4px; | ||
} | ||
} | ||
} |
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 you please refactor this file using BEM?
https://css-tricks.com/bem-101/
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.
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}}); | ||
}); |
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.
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.
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.
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.
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.
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
* @private | ||
* | ||
*/ | ||
_focusPreviousTab(element, elementInFocus) { |
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 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?
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.
Their primary functionality is the same, but they simply operate on different nodes. Combining them makes more mess than solve something.
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.
_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();
}
}
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.
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.
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.
Done.
@classNameBindings('isActive:is-active') | ||
@attributeBindings('tabindex') | ||
@attributeBindings('role') | ||
@attributeBindings('aria-labelledby') |
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.
@attributeBindings
accepts a list of values. So this can be refactored to a single line.
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.
Done.
* | ||
*/ | ||
init() { | ||
super.init(...arguments); |
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 lifecycle hook can be removed.
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.
Removed.
@attributeBindings('aria-controls') | ||
@attributeBindings('aria-selected') | ||
@attributeBindings('isDisabled:disabled') | ||
@attributeBindings('title') |
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.
Please refactor to single binding calls.
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.
Done.
*/ | ||
@action | ||
async activateTab(changedTo, event) { | ||
const _this = this; |
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.
Is this necessary? I don't see any scope changes within this function?
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.
Removed.
const _this = this; | ||
const beforeChange = get(_this, 'beforeChange'); | ||
const onChange = get(_this, 'onChange'); | ||
const currentTab = get(_this, 'selected'); |
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.
You could use getProperties
to batch the getter jobs.
https://api.emberjs.com/ember/release/functions/@ember%2Fobject/getProperties
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.
Done.
* | ||
*/ | ||
mouseDown() { | ||
if(get(this, 'isDisabled') === false) set(this, 'isPressed', true); |
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 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.
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.
Sure, will follow this hereon.
…ine if statements wrapped
* @private | ||
* | ||
*/ | ||
_focusPreviousTab(element, elementInFocus) { |
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.
_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) { |
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.
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.
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