Skip to content
This repository was archived by the owner on Aug 7, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions dist/oui-angular.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/oui-angular.min.js.map

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion packages/oui-angular/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import "@oui-angular/oui-slideshow/src";
import "@oui-angular/oui-page-header/src";
import "@oui-angular/oui-tile/src";
import "@oui-angular/oui-guide-menu/src";
import "@oui-angular/oui-header-tabs/src";

angular.module("oui", [
"oui.button",
Expand Down Expand Up @@ -69,5 +70,6 @@ angular.module("oui", [
"oui.slideshow",
"oui.page-header",
"oui.tile",
"oui.guide-menu"
"oui.guide-menu",
"oui.header-tabs"
]);
1 change: 1 addition & 0 deletions packages/oui-angular/src/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ loadTests(require.context("../../oui-slideshow/src/", true, /.*((\.spec)|(index)
loadTests(require.context("../../oui-page-header/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-tile/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-guide-menu/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-header-tabs/src/", true, /.*((\.spec)|(index))$/));

function loadTests (context) {
context.keys().forEach(context);
Expand Down
17 changes: 10 additions & 7 deletions packages/oui-dropdown/src/trigger/dropdown-trigger.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export default class {

if (this.$element[0].tagName.toLowerCase() === "oui-dropdown-trigger") {
this.$compile(template)(this.$scope, (clone) => {
this.$element.append(clone);
this.$element.replaceWith(clone);
this.$trigger = clone;
});
} else {
// Update custom $element
Expand All @@ -37,10 +38,12 @@ export default class {
})
.on("click", () => !this.disabled && this.dropdown.onTriggerClick())
.on("blur", evt => this.dropdown.triggerBlurHandler(evt));

this.$trigger = this.$element;
}

// Set the trigger to the parent component
this.dropdown.setDropdownTrigger(this.$element[0], this);
this.dropdown.setDropdownTrigger(this.$trigger[0], this);
});
}

Expand All @@ -51,13 +54,13 @@ export default class {
}

afterOpen () {
this.$element.attr("aria-expanded", true);
this.$element[0].focus();
this.$element.on("keydown", evt => this.dropdown.triggerKeyHandler(evt));
this.$trigger.attr("aria-expanded", true);
this.$trigger[0].focus();
this.$trigger.on("keydown", evt => this.dropdown.triggerKeyHandler(evt));
}

afterClose () {
this.$element.attr("aria-expanded", false);
this.$element.off("keydown");
this.$trigger.attr("aria-expanded", false);
this.$trigger.off("keydown");
}
}
87 changes: 87 additions & 0 deletions packages/oui-header-tabs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Header tabs

<component-status cx-design="complete" ux="rc"></component-status>

## Usage

### Normal

```html:preview
<oui-header-tabs>
<oui-header-tabs-item text="Home" href="/#"></oui-header-tabs-item>
<oui-header-tabs-item text="Page Header" href="/#!/oui-angular/page-header"></oui-header-tabs-item>
<oui-header-tabs-item text="Header Tabs" href="/#!/oui-angular/header-tabs" active></oui-header-tabs-item>
<oui-header-tabs-item text="Pagination" href="/#!/oui-angular/pagination"></oui-header-tabs-item>
<oui-header-tabs-item text="Datagrid" href="/#!/oui-angular/datagrid"></oui-header-tabs-item>
</oui-header-tabs>
```

### With dropdown
```html:preview
<oui-header-tabs>
<oui-header-tabs-item text="Home" href="/#"></oui-header-tabs-item>
<oui-header-tabs-dropdown text="Form">
<oui-header-tabs-item text="Field" href="/#!/oui-angular/field"></oui-header-tabs-item>
<oui-header-tabs-divider></oui-header-tabs-divider>
<oui-header-tabs-item text="Checkbox" href="#!/oui-angular/checkbox"></oui-header-tabs-item>
<oui-header-tabs-item text="Radio" href="#!/oui-angular/radio-group"></oui-header-tabs-item>
<oui-header-tabs-divider></oui-header-tabs-divider>
<oui-header-tabs-item text="Select" href="/#!/oui-angular/select"></oui-header-tabs-item>
<oui-header-tabs-item text="Select Picker" href="/#!/oui-angular/select-picker"></oui-header-tabs-item>
</oui-header-tabs-dropdown>
<oui-header-tabs-dropdown text="Datagrid">
<oui-header-tabs-item text="Datagrid" href="/#!/oui-angular/datagrid"></oui-header-tabs-item>
<oui-header-tabs-item text="Criteria Adder" href="#!/oui-angular/criteria-adder"></oui-header-tabs-item>
</oui-header-tabs-dropdown>
<oui-header-tabs-item text="Page Header" href="/#!/oui-angular/page-header"></oui-header-tabs-item>
<oui-header-tabs-item text="Header Tabs" href="/#!/oui-angular/header-tabs" active></oui-header-tabs-item>
</oui-header-tabs>
```

### In Page Header

```html:preview
<oui-page-header heading="Title" description="Description">
<oui-header-tabs>
<oui-header-tabs-item text="Home" href="/#"></oui-header-tabs-item>
<oui-header-tabs-item text="Page Header" href="/#!/oui-angular/page-header"></oui-header-tabs-item>
<oui-header-tabs-item text="Header Tabs" href="/#!/oui-angular/header-tabs" active></oui-header-tabs-item>
<oui-header-tabs-dropdown text="Form">
<oui-header-tabs-item text="Field" href="/#!/oui-angular/field"></oui-header-tabs-item>
<oui-header-tabs-divider></oui-header-tabs-divider>
<oui-header-tabs-item text="Checkbox" href="#!/oui-angular/checkbox"></oui-header-tabs-item>
<oui-header-tabs-item text="Radio" href="#!/oui-angular/radio-group"></oui-header-tabs-item>
<oui-header-tabs-divider></oui-header-tabs-divider>
<oui-header-tabs-item text="Select" href="/#!/oui-angular/select"></oui-header-tabs-item>
<oui-header-tabs-item text="Select Picker" href="/#!/oui-angular/select-picker"></oui-header-tabs-item>
</oui-header-tabs-dropdown>
<oui-header-tabs-item text="Pagination" href="/#!/oui-angular/pagination"></oui-header-tabs-item>
<oui-header-tabs-item text="Datagrid" href="/#!/oui-angular/datagrid"></oui-header-tabs-item>
</oui-header-tabs>
</oui-page-header>
```

## API

### oui-header-tabs-item

| Attribute | Type | Binding | One-time Binding | Values | Default | Description
| ---- | ---- | ---- | ---- | ---- | ---- | ----
| `text` | string | @ | yes | | | display the menu item with this text
| `href` | string | @? | yes | | | href of the menu item
| `state` | boolean | @? | yes | | | state of the menu item
| `stateParams` | object | <? | | | | state params of the menu item
| `external` | boolean | <? | yes | `true`, `false` | `false` | external link flag
| `active` | boolean | <? | | `true`, `false` | `false` | manual active flag

**Note:** `ui-router` is needed for the attributes `state` and `state-params`.

### oui-header-tabs-dropdown

| Attribute | Type | Binding | One-time Binding | Values | Default | Description
| ---- | ---- | ---- | ---- | ---- | ---- | ----
| `text` | string | @ | yes | | | display the dropdown with this text

### oui-header-tabs-divider

Add a separator for the dropdown menu
25 changes: 25 additions & 0 deletions packages/oui-header-tabs/src/header-tabs-dropdown.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import template from "./header-tabs-dropdown.html";

export default {
bindings: {
text: "@"
},
controller: class {
constructor ($element, $timeout) {
"ngInject";

this.$element = $element;
this.$timeout = $timeout;
}

$postLink () {
this.$timeout(() => {
this.$element
.addClass("oui-header-tabs__item oui-header-tabs__item_dropdown")
.attr("role", "listitem");
});
}
},
template,
transclude: true
};
6 changes: 6 additions & 0 deletions packages/oui-header-tabs/src/header-tabs-dropdown.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<oui-dropdown align="center" arrow>
<oui-dropdown-trigger text="{{::$ctrl.text}}"></oui-dropdown-trigger>
<oui-dropdown-content class="oui-header-tabs__dropdown-container" role="list"
ng-transclude>
</oui-dropdown-content>
</oui-dropdown>
15 changes: 15 additions & 0 deletions packages/oui-header-tabs/src/header-tabs-item.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import controller from "./header-tabs-item.controller";
import template from "./header-tabs-item.html";

export default {
controller,
template,
bindings: {
text: "@",
href: "@?",
state: "@?",
stateParams: "<?",
external: "<?",
active: "<?"
}
};
35 changes: 35 additions & 0 deletions packages/oui-header-tabs/src/header-tabs-item.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { addBooleanParameter } from "@oui-angular/common/component-utils";
import template from "./header-tabs-item.html";

export default class {
constructor ($attrs, $compile, $element, $scope, $timeout) {
"ngInject";

this.$attrs = $attrs;
this.$compile = $compile;
this.$element = $element;
this.$scope = $scope;
this.$timeout = $timeout;
}

$onInit () {
addBooleanParameter(this, "active");
addBooleanParameter(this, "external");

if (this.external) {
this.linkTarget = "_blank";
this.linkRel = "noopener";
}
}

$postLink () {
this.$compile(template)(this.$scope, clone => {
this.$element.replaceWith(clone);
});
}

// Return value of "ui-sref"
getFullSref () {
return `${this.state}(${JSON.stringify(this.stateParams)})`;
}
}
17 changes: 17 additions & 0 deletions packages/oui-header-tabs/src/header-tabs-item.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="oui-header-tabs__item" role="listitem"
ui-sref-active="oui-header-tabs__item_active"
ng-class="{ 'oui-header-tabs__item_active': $ctrl.active }">
<a ng-attr-rel="{{::$ctrl.linkRel}}"
ng-attr-target="{{::$ctrl.linkTarget}}"
ng-href="{{::$ctrl.href}}"
ng-if="::!!$ctrl.href">
<span ng-bind="::$ctrl.text"></span>
<span class="oui-icon oui-icon-external_link" aria-hidden="true"
ng-if="::$ctrl.external">
</span>
</a>
<a ng-bind="::$ctrl.text"
ng-if="::!!$ctrl.state"
ui-sref="{{::$ctrl.getFullSref()}}">
</a>
</div>
8 changes: 8 additions & 0 deletions packages/oui-header-tabs/src/header-tabs.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import controller from "./header-tabs.controller";
import template from "./header-tabs.html";

export default {
template,
controller,
transclude: true
};
128 changes: 128 additions & 0 deletions packages/oui-header-tabs/src/header-tabs.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
const checkScrollDelay = 800;

export default class {
constructor ($attrs, $element, $interval, $scope, $timeout, $window) {
"ngInject";

this.$attrs = $attrs;
this.$element = $element;
this.$interval = $interval;
this.$scope = $scope;
this.$timeout = $timeout;
this.$window = $window;
}

$onInit () {
this.scroll = {
begin: 0,
end: 0
};
}

$onDestroy () {
angular.element(this._tabsElement).off("scroll");
angular.element(this.$window).off("resize");
}

$postLink () {
this.$timeout(() => {
this.$element
.addClass("oui-header-tabs");

this._tabsElement = this.$element[0].querySelector(".oui-header-tabs__container");
angular.element(this._tabsElement).on("scroll", event => this._checkScroll(event));
angular.element(this.$window).on("resize", event => this._checkScroll(event));
this._initialCheck();
});

/* On initial render, we need to wait few seconds before calling
the checkScroll method otherwise panel size would be wrong. */
this.$timeout(() => this._initialCheck(), checkScrollDelay);
}

scrollLeft () {
this._scroll("left");
}

scrollRight () {
this._scroll("right");
}

_initialCheck () {
const activeTab = this.$element[0].querySelector(".oui-header-tabs__item_active");
if (activeTab && activeTab.offsetLeft - this._tabsElement.offsetLeft > 0) {
this._tabsElement.scrollLeft = activeTab.offsetLeft - this._tabsElement.offsetLeft;
} else {
this.scroll.end = this._tabsElement.scrollWidth - this._tabsElement.offsetWidth;
}
}

_scroll (direction) {
const itemToGo = this._findItemToGo(direction);
this._scrollToItem(direction, itemToGo);
}

_checkScroll (e) {
if (e) {
e.preventDefault();
}

this.scroll.begin = this._tabsElement.scrollLeft;
this.scroll.end = this._tabsElement.scrollWidth - this._tabsElement.offsetWidth - this._tabsElement.scrollLeft;
this.$scope.$digest();
}

_findItemToGo (direction) {
const tabsList = [].slice.call(this._tabsElement.querySelectorAll(":scope > .oui-header-tabs__item"));
const tabsOffset = this._tabsElement.offsetLeft;
const tabsStart = this._tabsElement.scrollLeft;
const tabsEnd = tabsStart + this._tabsElement.offsetWidth;

let itemGutter = 0;
if (tabsList && tabsList.length > 1) {
itemGutter = tabsList[1].offsetLeft - (tabsList[0].offsetLeft + tabsList[0].offsetWidth);
}

let itemToGo = tabsList[0];
for (const item of tabsList) {
const itemStart = item.offsetLeft - tabsOffset;
if (direction === "right" && itemStart <= tabsEnd + tabsOffset + itemGutter) {
itemToGo = item;
} else if (direction === "left" && itemStart < tabsStart) {
itemToGo = item;
} else {
break;
}
}
return itemToGo;
}

_scrollToItem (direction, item) {
const duration = 500;
const stepDuration = 15;
const step = this._tabsElement.scrollWidth / (duration / stepDuration);

const itemStart = item.offsetLeft - this._tabsElement.offsetLeft;
const itemEnd = itemStart + item.offsetWidth;
const tabsWidth = this._tabsElement.offsetWidth;

const loop = this.$interval(() => {
const tabsStart = this._tabsElement.scrollLeft;
const tabsEnd = this._tabsElement.scrollWidth - this._tabsElement.offsetWidth - this._tabsElement.scrollLeft;
const screenEnd = tabsStart + this._tabsElement.offsetWidth;

if (direction === "right" && tabsEnd > 0 && (tabsStart + step < itemStart || itemEnd > screenEnd)) {
this._tabsElement.scrollLeft += step;
} else if (direction === "left" && tabsStart > 0 && (screenEnd - step > itemEnd || itemStart < tabsStart - step)) {
this._tabsElement.scrollLeft -= step;
} else {
if (direction === "right") {
this._tabsElement.scrollLeft = tabsStart <= itemStart ? itemStart : itemEnd - tabsWidth;
} else {
this._tabsElement.scrollLeft = tabsStart >= itemStart ? itemStart : itemEnd - tabsWidth;
}
this.$interval.cancel(loop);
}
}, stepDuration);
}
}
Loading