mirror of https://github.com/hashicorp/consul
ui: Tab Improvements (animations/branding) (#7772)
* ui: Adds a tab selection animation to our app tabs 1. Replace all mentions of `magenta` with a themeable CSS property. 2. Add an easy way to inline style DOM nodes 3. Use CSS properties to add tab animation * Fix up rendering test * Avoid DOM noodling as much as possiblepull/7344/head
parent
12e2c93e6b
commit
135d22586b
|
@ -1,17 +1,21 @@
|
|||
<nav role="tablist" class="tab-nav">
|
||||
<nav
|
||||
style={{if selectedWidth (concat '--selected-width:' selectedWidth ';--selected-left:' selectedLeft ';--selected-height:' selectedHeight ';--selected-top:' selectedTop) undefined}}
|
||||
role="tablist"
|
||||
class={{concat 'tab-nav' (if isAnimatable ' animatable' '')}}
|
||||
id={{guid}}>
|
||||
<ul>
|
||||
{{#each items as |item|}}
|
||||
<li
|
||||
data-test-tab={{concat name '_' (if item.label (slugify item.label) (slugify item))}}
|
||||
class={{if (or item.selected (eq selected (if item.label (slugify item.label) (slugify item)))) 'selected'}}
|
||||
>
|
||||
<label role="tab" onkeydown={{action 'keydown'}} tabindex="0" aria-controls="radiogroup_{{name}}_{{if item.label (slugify item.label) (slugify item)}}_panel" for="radiogroup_{{name}}_{{if item.label (slugify item.label) (slugify item)}}" data-test-radiobutton="{{name}}_{{if item.label (slugify item.label) (slugify item)}}">
|
||||
{{#if item.href }}
|
||||
<a href={{item.href}}>{{item.label}}</a>
|
||||
{{else}}
|
||||
<a>{{ item }}</a>
|
||||
{{/if}}
|
||||
<label role="tab" onkeydown={{action 'keydown'}} tabindex="0" aria-controls="radiogroup_{{name}}_{{if item.label (slugify item.label) (slugify item)}}_panel" for="radiogroup_{{name}}_{{if item.label (slugify item.label) (slugify item)}}" data-test-radiobutton="{{name}}_{{if item.label (slugify item.label) (slugify item)}}">
|
||||
<a>{{item}}</a>
|
||||
</label>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
|
|
@ -1,9 +1,39 @@
|
|||
import Component from '@ember/component';
|
||||
import { setProperties, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { schedule } from '@ember/runloop';
|
||||
|
||||
const ENTER = 13;
|
||||
const SELECTED = 'li.selected';
|
||||
export default Component.extend({
|
||||
name: 'tab',
|
||||
tagName: '',
|
||||
dom: service('dom'),
|
||||
isAnimatable: false,
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.guid = this.dom.guid(this);
|
||||
},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
this.$nav = this.dom.element(`#${this.guid}`);
|
||||
this.select(this.dom.element(SELECTED, this.$nav));
|
||||
set(this, 'isAnimatable', true);
|
||||
},
|
||||
didUpdateAttrs: function() {
|
||||
schedule('afterRender', () => this.select(this.dom.element(SELECTED, this.$nav)));
|
||||
},
|
||||
select: function($el) {
|
||||
if (!$el) {
|
||||
return;
|
||||
}
|
||||
setProperties(this, {
|
||||
selectedWidth: $el.offsetWidth,
|
||||
selectedLeft: $el.offsetLeft,
|
||||
selectedHeight: $el.offsetHeight,
|
||||
selectedTop: $el.offsetTop,
|
||||
});
|
||||
},
|
||||
actions: {
|
||||
keydown: function(e) {
|
||||
if (e.keyCode === ENTER) {
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
%with-transition-500 {
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
%blink-in-fade-out {
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.1s;
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
/* same as decor-border-000 - but need to think about being able to import color on its own*/
|
||||
border-style: solid;
|
||||
}
|
||||
%frame-brand-300 {
|
||||
@extend %frame-border-000;
|
||||
background-color: $transparent;
|
||||
border-color: var(--decor-brand-600, inherit);
|
||||
color: var(--typo-brand-600, inherit);
|
||||
}
|
||||
|
||||
/* possibly filter bar */
|
||||
/* modal close button */
|
||||
|
|
|
@ -40,6 +40,5 @@
|
|||
}
|
||||
%form-element-note > code {
|
||||
background-color: $gray-200;
|
||||
/* consul color but its a code looking style?*/
|
||||
color: $magenta-600;
|
||||
color: var(--typo-brand-600, inherit);
|
||||
}
|
||||
|
|
|
@ -31,5 +31,5 @@
|
|||
}
|
||||
%menu-panel .is-active > *::after {
|
||||
@extend %with-check-plain-mask, %as-pseudo;
|
||||
background-color: $magenta-600;
|
||||
background-color: var(--swatch-brand-600, $black);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
@import './skin';
|
||||
@import './layout';
|
||||
%with-animated-tab-selection ul::after,
|
||||
%tab-button-selected {
|
||||
@extend %frame-brand-300;
|
||||
}
|
||||
%tab-nav li a {
|
||||
@extend %tab-button;
|
||||
}
|
||||
|
|
|
@ -2,19 +2,25 @@
|
|||
clear: both;
|
||||
}
|
||||
%tab-nav ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
%tab-button {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
%with-animated-tab-selection ul::after {
|
||||
@extend %as-pseudo, %with-transition-500;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 0;
|
||||
border-top: 0;
|
||||
width: calc(var(--selected-width, 0) * 1px);
|
||||
transform: translate(calc(var(--selected-left, 0) * 1px), 0);
|
||||
transition-property: transform, width;
|
||||
}
|
||||
%tab-button {
|
||||
display: inline-block;
|
||||
padding-top: 13px;
|
||||
padding-bottom: 13px;
|
||||
padding: 16px 13px;
|
||||
}
|
||||
%tab-section section h3 {
|
||||
margin: 24px 0;
|
||||
|
|
|
@ -1,13 +1,3 @@
|
|||
%tab-nav {
|
||||
/* %frame-gray-something */
|
||||
border-bottom: $decor-border-100;
|
||||
/* TODO: structure tabs don't actually have a top border */
|
||||
border-top: $decor-border-200;
|
||||
}
|
||||
%tab-nav {
|
||||
/* %frame-gray-something */
|
||||
border-color: $gray-200;
|
||||
}
|
||||
%tab-nav ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
@ -18,16 +8,29 @@
|
|||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
}
|
||||
%tab-nav {
|
||||
/* %frame-transparent-something */
|
||||
border-bottom: $decor-border-100;
|
||||
}
|
||||
%tab-nav {
|
||||
/* %frame-transparent-something */
|
||||
border-color: $gray-200;
|
||||
}
|
||||
%with-animated-tab-selection ul::after,
|
||||
%tab-button {
|
||||
border-bottom: $decor-border-300;
|
||||
}
|
||||
%tab-button {
|
||||
border-color: $color-transparent;
|
||||
@extend %with-transition-500;
|
||||
transition-property: background-color;
|
||||
border-color: $transparent;
|
||||
color: $gray-500;
|
||||
}
|
||||
%tab-button-intent,
|
||||
%tab-button-active {
|
||||
/* %frame-gray-something */
|
||||
border-color: $color-transparent;
|
||||
background-color: $gray-100;
|
||||
}
|
||||
%tab-nav.animatable .selected a {
|
||||
border-color: $transparent !important;
|
||||
}
|
||||
|
|
|
@ -12,19 +12,19 @@
|
|||
@extend %with-logo-github-monochrome-icon, %as-pseudo;
|
||||
}
|
||||
%main-header-horizontal::before {
|
||||
background-color: $magenta-600;
|
||||
background-color: var(--swatch-brand-600, $black);
|
||||
}
|
||||
%main-nav-horizontal-action,
|
||||
%main-nav-horizontal-toggle-button {
|
||||
color: $magenta-050;
|
||||
color: var(--typo-brand-050, $black);
|
||||
}
|
||||
@media #{$--lt-horizontal-nav} {
|
||||
%main-nav-horizontal-panel {
|
||||
background-color: $magenta-600;
|
||||
background-color: var(---swatch-brand-600, $black);
|
||||
}
|
||||
}
|
||||
@media #{$--horizontal-nav} {
|
||||
%main-nav-horizontal-action-active {
|
||||
background-color: $magenta-800;
|
||||
background-color: var(--swatch-brand-800, $black);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
.tab-nav {
|
||||
@extend %tab-nav;
|
||||
}
|
||||
%tab-nav.animatable {
|
||||
@extend %with-animated-tab-selection;
|
||||
}
|
||||
.tab-section {
|
||||
@extend %tab-section;
|
||||
/* this keeps in-tab-section toolbars flush to the top, see Node Detail > Services */
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
%tab-button-selected {
|
||||
@extend %frame-magenta-300;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
td a.is-management::after {
|
||||
@extend %with-star-fill-mask, %as-pseudo;
|
||||
background-color: $magenta-600;
|
||||
background-color: var(--swatch-brand-600, $black);
|
||||
height: 16px;
|
||||
top: 2px;
|
||||
padding-left: 32px;
|
||||
|
|
|
@ -9,4 +9,23 @@
|
|||
--decor-radius-200: #{$decor-radius-200};
|
||||
--gray-500: #{$gray-500};
|
||||
--decor-elevation-600: #{$decor-elevation-600};
|
||||
|
||||
/* base brand colors */
|
||||
--brand-050: #{$magenta-050};
|
||||
// --brand-100: #{$magenta-100};
|
||||
// --brand-200: #{$magenta-200};
|
||||
// --brand-300: #{$magenta-300};
|
||||
// --brand-400: #{$magenta-400};
|
||||
// --brand-500: #{$magenta-500};
|
||||
--brand-600: #{$magenta-600};
|
||||
// --brand-700: #{$magenta-700};
|
||||
--brand-800: #{$magenta-800};
|
||||
// --brand-900: #{$magenta-900};
|
||||
|
||||
/* themeable brand colors */
|
||||
--typo-brand-050: var(--brand-050);
|
||||
--typo-brand-600: var(--brand-600);
|
||||
--decor-brand-600: var(--brand-600);
|
||||
--swatch-brand-600: var(--brand-600);
|
||||
--swatch-brand-800: var(--brand-800);
|
||||
}
|
||||
|
|
|
@ -10,15 +10,15 @@ module('Integration | Component | tab nav', function(hooks) {
|
|||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.on('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`{{tab-nav}}`);
|
||||
|
||||
assert.dom('*').hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
{{#tab-nav}}{{/tab-nav}}
|
||||
<TabNav @items={{array
|
||||
(hash
|
||||
label="Tab Label"
|
||||
href="/"
|
||||
)
|
||||
}} />
|
||||
`);
|
||||
|
||||
assert.dom('*').hasText('');
|
||||
assert.dom('*').hasText('Tab Label');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function(name, items, blankKey = 'all') {
|
|||
...prev,
|
||||
...{
|
||||
[`${key}IsSelected`]: is('.selected', `[data-test-tab="${name}_${item}"]`),
|
||||
[key]: clickable(`[data-test-tab="${name}_${item}"] > label > a`),
|
||||
[key]: clickable(`[data-test-tab="${name}_${item}"] a`),
|
||||
},
|
||||
};
|
||||
}, {});
|
||||
|
|
Loading…
Reference in New Issue