-
Notifications
You must be signed in to change notification settings - Fork 327
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
Add tabs component #776
Add tabs component #776
Conversation
src/components/tabs/tabs.js
Outdated
var id = $panel.id | ||
$panel.id = '' | ||
this.changingHash = true | ||
window.location.hash = this.getHref($tab).slice(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.
Are we sure we want to be adding the history stack?
User needs:
- As a user I want to be able to copy and paste the URL and have tabs show in the same way when I go to this URL
- As a user I want to be able to go back to the previous page, despite how many tabs I've interacted with.
With this in mind, I recommend we consider using replaceState
if (typeof window.matchMedia === 'function') { | ||
this.setupResponsiveChecks() | ||
} else { | ||
this.setup() |
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.
Previously, this feature detect was around the whole component which meant that on small screens no tabs will be setup.
Am I right in thinking that in browsers that don't support matchMedia, they'll get tabs regardless of screen size?
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.
Given that the mobile browsers that we support all have good support for matchMedia maybe this isn't too much of a worry.
With the assumption that most of the time IE9 and below are used by users on desktop machines with wide screens?
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, it's probably okay. Just to note two examples where it's not as robust:
An IE9 user who opens up two windows side by side will get tabs in a small-ish screen.
Similarly, Blackberry 7, for example, will get tabs on their small screen.
In both cases, they will experience some layout issues such as horizontal scrolling or wrapping of tabs.
Just to note I think the example needs changing - this example is a pattern that does not have clear labels, and is no longer used by gov.uk The labels issue was brought up in the working group review: alphagov/govuk-design-system-backlog#100 (comment) |
91ca602
to
d830559
Compare
Hey @joelanman - how about this example from the Judicial Case Manager?: (the respondent tab has a similar table in it) |
It's ok, I'm sure in context for these users that the labels are clear, but it would be nice if we could find one where the labels are just clear to most people. |
@joelanman - here is a fictional but I think plausible alternative: Each tab has a similar table, but the numbers are different. Incidentally (@adamsilver you might have an opinion on this) - do we think that each tab should have a section heading? I don't think the above example needs them, but they might be necessary for accessibility / non-JS... |
I don't think the Tab component needs to dictate whether each panel should be marked up with a heading or not. If headings are needed, then they'll be added to the HTML. If not, they won't be. The Tab component itself does provide an accessible label (labelled by the panel's related tab label) so that's non-sighted users sorted. If there's a scenario whereby we want headings without JS but no (visual) headings with JS, then I'd imagine the heading would be given a class of |
a2bdc29
to
720371d
Compare
src/components/tabs/README.md
Outdated
|
||
## Introduction | ||
|
||
Component for conditionally revealing content, using the details HTML element. |
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 this is right
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.
good spot, thanks!
src/components/tabs/index.njk
Outdated
], | ||
[ | ||
{ | ||
text: 'item.id' |
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.
we changed the way we show item properties from item.id
to items.{}.id
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.
Updated all the item properties and borrowed
he explanations for consistency.
69375e7
to
d380465
Compare
099f400
to
8f3983b
Compare
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 had a chance to go through the tests yet.
src/components/tabs/_tabs.scss
Outdated
} | ||
|
||
// JavaScript enabled | ||
.js-enabled .govuk-tabs { |
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 try and keep specificity to a minimum, should this just be .js-enabled
?
src/components/tabs/tabs.js
Outdated
this.checkMode(this.mql) | ||
} | ||
|
||
Tabs.prototype.checkMode = function (mql) { |
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.
mql
is passed in but never used - within the function we use this.mql
.
src/components/tabs/tabs.js
Outdated
Tabs.prototype.setupResponsiveChecks = function () { | ||
this.mql = window.matchMedia('(min-width: 40.0625em)') | ||
this.mql.addListener(this.checkMode.bind(this)) | ||
this.checkMode(this.mql) |
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.
Passing this.mql
unnecessarily as checkMode
never uses it.
src/components/tabs/tabs.js
Outdated
|
||
// Remove old active panels | ||
this.unhighlightTab($tab) | ||
this.hidePanel($tab) |
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 we change these two calls for hideTab
which wraps them both up together?
src/components/tabs/tabs.js
Outdated
var $activeTab = this.getTab(window.location.hash) | ||
if ($activeTab) { | ||
this.highlightTab($activeTab) | ||
this.showPanel($activeTab) |
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.
Likewise I believe both highlightTab
and showPanel
can be replaced by showTab
?
src/components/tabs/tabs.js
Outdated
this.changingHash = false | ||
return | ||
} | ||
var $hashTab = this.getTab(window.location.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.
Can use hash
rather than window.location.hash
.
Would also consider skipping hasTab
and storing and using the result from getTab
which would mirror what we do in setup (var $activeTab = this.getTab(window.location.hash)
)
src/components/tabs/tabs.js
Outdated
} | ||
var $hashTab = this.getTab(window.location.hash) | ||
var $currentTab = this.getCurrentTab() | ||
if ($hashTab) { |
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 must be true if we got here? Because we can only reach this code if we didn't return as a result of !this.hasTab
.
src/components/tabs/tabs.js
Outdated
this.showTab($hashTab) | ||
$hashTab.focus() | ||
} else { | ||
var $firstTab = this.$tabs.firstElementChild |
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 is dead code that can be removed, but if not it'd be good to reduce the duplication.
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 dead code, as when there's not hash it falls back to the first tab. Will try and reduce the logic.
src/components/tabs/tabs.js
Outdated
} | ||
|
||
Tabs.prototype.hasTab = function (hash) { | ||
return this.$module.querySelector(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.
Is there a reason this uses a different selector to getTab
?
|
||
function Tabs ($module) { | ||
this.$module = $module | ||
this.$tabs = $module.querySelectorAll('.govuk-tabs__tab') |
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.
Have we tested having multiple tab components on a single page?
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, did tested with multiple per page.
8f3983b
to
66d8a06
Compare
66d8a06
to
e8b32f6
Compare
e8b32f6
to
57a4a48
Compare
Updated addressing all comments. |
57a4a48
to
0238a7f
Compare
src/components/tabs/tabs.js
Outdated
Tabs.prototype.createHistoryEntry = function ($tab) { | ||
var $panel = this.getPanel($tab) | ||
var id = $panel.id | ||
$panel.id = '' |
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.
Why do we have to null and restore the panel's id
? If this is behaviour we definitely need, can we add a comment to explain why?
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.
It is so the page doesn't jump when a user clicks a tab (which changes the
hash).
@@ -31,6 +32,11 @@ function initAll () { | |||
nodeListForEach($radios, function ($radio) { | |||
new Radios($radio).init() | |||
}) | |||
|
|||
var $tabs = document.querySelectorAll('[data-module="tabs"]') |
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.
At the moment the macro template has the data attribute on the container. This means that if I use the macro, it will automatically get instantiated into tabs.
This could become problematic if I want to do something more custom. Like listen to an event fired by the instance (I know we can't do that right now).
Is GOV.UK Frontend continuing to instantiate automatically like 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.
Your concern is valid, but this is consistent with how the other components are being initialised. Will make a note and look into that for all the components.
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.
Yep, it might be okay, broadly speaking, for say tabs and conditional radio button reveals. But as a general approach it can be overly constraining.
Thanks @alex-ju
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 use case is already accounted for: you can avoid initAll()
and call the constructor yourself.
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.
Since we did work to stop initAll()
from executing automatically :)
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, not initialising or initialising separately a specific component is also an option.
When Adam mentioned 'something custom', I was thinking of a case when you want to initialise but hook different listeners or add custom functionality to a Tabs instance before doing that, which I think is still possible with the current model:
var myTabs = new Tabs($tabs)
myTabs.myCustomFunction = function () { ... }
myTabs.init()
Will the |
0238a7f
to
ad9675a
Compare
Co-authored-by: adamsilver <[email protected]>
Co-authored-by: adamsilver <[email protected]>
ad9675a
to
7fe359e
Compare
@adamsilver: the Tabs constructor is attached to a GOVUKFrontend global variable and every tab instance will be initialised separately using the For example if we consider |
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.
LGTM! 👍📑
Review page
https://govuk-frontend-review-pr-776.herokuapp.com/components/tabs
Contribution log
alphagov/govuk-design-system-backlog#100 (comment)
Browser and AT testing
Chrome 67
Safari 11
Firefox 60 with inverted colours
IE11
IE10
IE9
IE8
NVDA with Firefox
Tab control. 'Past day' tab, selected, 1 of 4.
JAWS with IE11
'Past day' tab, selected, 1 of 4.
VoiceOver on Safari
'Past day', selected, tab, 1 of 4.
Progress
Trello card