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

Toolbar #397

Merged
merged 12 commits into from
Dec 13, 2017
Merged

Toolbar #397

merged 12 commits into from
Dec 13, 2017

Conversation

bitpshr
Copy link
Member

@bitpshr bitpshr commented Nov 30, 2017

Type: feature

The following has been addressed in the PR:

  • There is a related issue
  • All code matches the style guide
  • Unit or Functional tests are included in the PR

Description:

This PR implements an opinionated Toolbar component that can configured to use either fixed or static positioning. Users can provide a title element and action elements. Action elements will optionally be rendered in a SlidePane when the component width is less than the collapseWidth. Both the title and actions can be any DNode, which means events can be attached directly to those elements instead of being propagated up through the Toolbar itself.

Example

Resolves #386

@@ -32,7 +33,7 @@ export class App extends WidgetBase<WidgetProperties> {
}

render() {
return v('div', [
return v('div', { id: 'module-select' }, [
Copy link
Member Author

Choose a reason for hiding this comment

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

The Toolbar uses a flexbox-based layout wherein page content should be a child of the toolbar itself. This negates the need for any padding-top on the <body> element to account for a fixed-positioned toolbar, and allows the toolbar to be an arbitrary height while still allowing page content to scroll independently.

Unfortunately, because our example pages render this module select UI before any component is rendered, it's impossible to place this inside the Toolbar example. To get around this, the toolbar example page adds a single CSS rule to the example page to style the select in a more sane manner, hence this added ID.

* Properties that can be set on a Toolbar component
*
* @property actions Action elements to show in the toolbar
* @property collapseWidth Width at which to collapse actions into a SlidePane
Copy link
Member Author

Choose a reason for hiding this comment

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

I feel strongly that we shouldn't always be inverting control to the parent for every piece of UI functionality. It's both tedious and common enough to hide action items in a SlidePane that our opinionated component should provide this, while also allowing easy extension. In practice, using a property like collapseWidth instead of collapsed and internalizing window resizing logic felt much cleaner.

Copy link
Member

Choose a reason for hiding this comment

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

I'm happy with that here

Copy link
Member

Choose a reason for hiding this comment

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

Might want an onCollapse(collapsed: boolean) callback though to notify parents?

constructor() {
super();
this._onResize = debounce(this._collapseIfNecessary, 250);
window.addEventListener('resize', this._onResize);
Copy link
Member Author

Choose a reason for hiding this comment

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

This is stopgap until dojo/widget-core#618 lands. For now, we do the best we can by debouncing a resize handler that's explicitly removed on detachment.

Copy link
Member

Choose a reason for hiding this comment

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

Does this need to be debounced given that it will already be limited by requestAnimationFrame

}

onAttach() {
const style = document.createElement('style');
Copy link
Member Author

Choose a reason for hiding this comment

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

This method is purely for demo styling purposes. See above comment re: Toolbar and its children.

overflow-y: auto;
}

.action { }
Copy link
Member Author

Choose a reason for hiding this comment

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

We may not need all these classes to be themeable, but it felt right to expose as many of them as possible.

@import './variables.css';

.toolbar {
background: #FFF;
Copy link
Member

Choose a reason for hiding this comment

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

can this be moved to a var?

padding: var(--grid-base);
position: absolute;
right: var(--grid-base);
top: 50%;
Copy link
Member

Choose a reason for hiding this comment

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

can you not use align-items: center on the parent to achieve this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sort of, but it would require one extra element. Since I'm already absolutely positioning the button to the right it was a bit cleaner (and more uniform) to just use top and transform.

Copy link
Member

Choose a reason for hiding this comment

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

what about when our users want to use flexbox? If adding an extra element to make this simpler and removes absolute positioning etc is it not worth it?

* Properties that can be set on a Toolbar component
*
* @property actions Action elements to show in the toolbar
* @property collapseWidth Width at which to collapse actions into a SlidePane
Copy link
Member

Choose a reason for hiding this comment

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

I'm happy with that here

* Properties that can be set on a Toolbar component
*
* @property actions Action elements to show in the toolbar
* @property collapseWidth Width at which to collapse actions into a SlidePane
Copy link
Member

Choose a reason for hiding this comment

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

Might want an onCollapse(collapsed: boolean) callback though to notify parents?

constructor() {
super();
this._onResize = debounce(this._collapseIfNecessary, 250);
window.addEventListener('resize', this._onResize);
Copy link
Member

Choose a reason for hiding this comment

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

Does this need to be debounced given that it will already be limited by requestAnimationFrame

return this._collapsed ? w(SlidePane, {
align: Align.right,
closeText: 'close menu',
key: 'menu',
Copy link
Member

Choose a reason for hiding this comment

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

not sure you should have two elements with the same key

protected renderTitle(): DNode {
const { title } = this.properties;

return title ? v('div', {
Copy link
Member

Choose a reason for hiding this comment

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

no h1 ?

];
}

protected getModifierClasses(): (string | null)[] {
Copy link
Member

Choose a reason for hiding this comment

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

renamed these to getRootClasses elsewhere

Copy link
Member

@tomdye tomdye left a comment

Choose a reason for hiding this comment

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

Just the question re. flex remains

Copy link
Contributor

@smhigley smhigley left a comment

Choose a reason for hiding this comment

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

I like it! Looks like a great addition to widgets :)

height: 100%;
left: 0;
overflow: hidden;
position: fixed;
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to be fixed? It also works with position: absolute, and then it isn't constrained to only being document-level (although a toolbar on anything else seems weird, granted).

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch. The fixed styling was left over from the approach that didn't rely on content being a child of the Toolbar.

const actionsElements = actions.map((action, index) => v('div', {
classes: [ this.theme(css.action) ],
key: index
}, [ action ]));
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really understand why these need to be wrapped in a div. Is it just so their css/theming is within the toolbar CSS as opposed to in parent-widget-level CSS?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this is really just for easier theming. Most situations I thought of were pretty difficult to accomplish without actions being grouped in a common parent. I'm open to removing it if necessary though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, I kind of imagine most of the time the actions will be passed in already wrapped in a parent, for semantics and styling. E.g.:

w(Toolbar, {
   actions: [
      v('div', { id: 'menu', role: 'menubar' }, [
         v('a', { href: '/#home' }, [ 'Home' ]),
         v('a', { href: '/#about' }, [ 'About' ]),
         v('a', { href: '/#contact' }, [ 'Contact' ])
      ])
   ],
   title: 'Foobar'
}, [
   content
])

In that case the extra wrapping div seems a little unnecessary. I don't really dislike it, I just think it introduces an extra wrapping element where none is needed.


return title ? v('h1', {
classes: [ this.theme(css.title), css.titleFixed ]
}, [ title ]) : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this should really be an h1. Usually that should go to a content-specific title, not to a company name/logo, which is more common for toolbars. Since title is already a DNode, could we just return that, wrapped in a div for flex styling?

Copy link
Contributor

Choose a reason for hiding this comment

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

p.s.: really like the breakdown of the render fn :)

.then(displayed => {
assert.isFalse(displayed);
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I know functional tests are a pain, but maybe one to make sure the slidepane is shown or not shown based on window width would be good

Copy link
Contributor

@smhigley smhigley Dec 6, 2017

Choose a reason for hiding this comment

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

^ ignore that if it's too much time, or if you already tried and removed it in frustration

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this is a good idea, but I had the hardest time getting functional tests to run locally. I can open a new issue for this if that works for you.

@@ -12,7 +12,7 @@ import * as css from './styles/tooltip.m.css';
*
* @property content Information to show within the tooltip
* @property orientation Where this tooltip should render relative to its child
* @property open Determines if this tooltip is visible
* @property open Determines if this tooltip is visible
Copy link
Contributor

Choose a reason for hiding this comment

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

lol

]),
v('div', {
classes: [ this.theme(css.content), css.contentFixed ]
}, this.children)
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 this needs to be added now, but reversing the order of the toolbar and content based on a position property would be easy, and having the option of a bottom toolbar is nice. Works with the styling as-is, too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added!

classes: [ this.theme(css.menuButton), css.menuButtonFixed ],
onclick: this._toggleMenu
}, [
'open menu',
Copy link
Contributor

Choose a reason for hiding this comment

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

You probably already know this, but depending on the order of merging of this and Neil's i18n PR, the 'open menu'/'close menu' text should either be changed here, or Neil should know that you added some more strings for his PR

@codecov
Copy link

codecov bot commented Dec 8, 2017

Codecov Report

Merging #397 into master will increase coverage by 0.72%.
The diff coverage is 89.65%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #397      +/-   ##
==========================================
+ Coverage   97.14%   97.86%   +0.72%     
==========================================
  Files          26       27       +1     
  Lines        1997     2062      +65     
  Branches      517      531      +14     
==========================================
+ Hits         1940     2018      +78     
+ Misses         20        6      -14     
- Partials       37       38       +1
Impacted Files Coverage Δ
src/tooltip/Tooltip.ts 100% <ø> (ø) ⬆️
src/toolbar/Toolbar.ts 89.65% <89.65%> (ø)
src/enhancedtextinput/EnhancedTextInput.ts 100% <0%> (ø)
src/textinput/TextInput.ts 98.59% <0%> (+0.2%) ⬆️
src/tabcontroller/TabController.ts 99.04% <0%> (+0.95%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 645cb2d...66cfdca. Read the comment docs.

@bitpshr
Copy link
Member Author

bitpshr commented Dec 8, 2017

@smhigley This is ready for another review when you have a chance.


### Accessibility Features

- The menu button displays screen-readable text that's visually hidden and its icon has an `aria-hidden` attribute
Copy link
Contributor

Choose a reason for hiding this comment

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

Thinking about the menu wrapping, it might also be good to add some text like this:

Toolbar makes no assumptions about what content will be inserted through actions. If you wish to create a site-wide navigation section, it would be best to wrap links in an element with the appropriate semantics (e.g. <nav> or role="navigation")

Copy link
Contributor

@smhigley smhigley left a comment

Choose a reason for hiding this comment

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

MERGE ALL THE THINGS

@bitpshr bitpshr merged commit 4da7a15 into dojo:master Dec 13, 2017
@bitpshr bitpshr deleted the toolbar branch December 13, 2017 18:04
@dylans dylans added this to the beta.5 milestone Dec 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Toolbar Component
4 participants