From 79f2b3d0db7eebaf3bd06e75e6948d262f72a9fb Mon Sep 17 00:00:00 2001 From: Axel Peter Date: Fri, 16 Nov 2018 14:28:24 +0100 Subject: [PATCH 1/5] feat(oui-tabs): add tabs component --- packages/oui-angular/src/index.js | 2 + packages/oui-angular/src/index.spec.js | 1 + packages/oui-tabs/README.md | 81 +++++++++++ packages/oui-tabs/package.json | 7 + packages/oui-tabs/src/index.js | 8 ++ packages/oui-tabs/src/index.spec.js | 133 ++++++++++++++++++ .../oui-tabs/src/item/tabs-item.component.js | 17 +++ .../oui-tabs/src/item/tabs-item.controller.js | 64 +++++++++ packages/oui-tabs/src/item/tabs-item.html | 19 +++ packages/oui-tabs/src/tabs.component.js | 13 ++ packages/oui-tabs/src/tabs.controller.js | 51 +++++++ packages/oui-tabs/src/tabs.html | 18 +++ 12 files changed, 414 insertions(+) create mode 100644 packages/oui-tabs/README.md create mode 100644 packages/oui-tabs/package.json create mode 100644 packages/oui-tabs/src/index.js create mode 100644 packages/oui-tabs/src/index.spec.js create mode 100644 packages/oui-tabs/src/item/tabs-item.component.js create mode 100644 packages/oui-tabs/src/item/tabs-item.controller.js create mode 100644 packages/oui-tabs/src/item/tabs-item.html create mode 100644 packages/oui-tabs/src/tabs.component.js create mode 100644 packages/oui-tabs/src/tabs.controller.js create mode 100644 packages/oui-tabs/src/tabs.html diff --git a/packages/oui-angular/src/index.js b/packages/oui-angular/src/index.js index 745f69b4..300d3744 100644 --- a/packages/oui-angular/src/index.js +++ b/packages/oui-angular/src/index.js @@ -33,6 +33,7 @@ import Slideshow from "@ovh-ui/oui-slideshow"; import Spinner from "@ovh-ui/oui-spinner"; import Stepper from "@ovh-ui/oui-stepper"; import Switch from "@ovh-ui/oui-switch"; +import Tabs from "@ovh-ui/oui-tabs"; import Textarea from "@ovh-ui/oui-textarea"; import Tile from "@ovh-ui/oui-tile"; import Tooltip from "@ovh-ui/oui-tooltip"; @@ -74,6 +75,7 @@ export default angular Spinner, Stepper, Switch, + Tabs, Textarea, Tile, Tooltip diff --git a/packages/oui-angular/src/index.spec.js b/packages/oui-angular/src/index.spec.js index 42ff6b48..4835b9e8 100644 --- a/packages/oui-angular/src/index.spec.js +++ b/packages/oui-angular/src/index.spec.js @@ -35,6 +35,7 @@ loadTests(require.context("../../oui-slideshow/src/", true, /.*((\.spec)|(index) loadTests(require.context("../../oui-spinner/src/", true, /.*((\.spec)|(index))$/)); loadTests(require.context("../../oui-stepper/src/", true, /.*((\.spec)|(index))$/)); loadTests(require.context("../../oui-switch/src/", true, /.*((\.spec)|(index))$/)); +loadTests(require.context("../../oui-tabs/src/", true, /.*((\.spec)|(index))$/)); loadTests(require.context("../../oui-tile/src/", true, /.*((\.spec)|(index))$/)); loadTests(require.context("../../oui-textarea/src/", true, /.*((\.spec)|(index))$/)); loadTests(require.context("../../oui-tooltip/src/", true, /.*((\.spec)|(index))$/)); diff --git a/packages/oui-tabs/README.md b/packages/oui-tabs/README.md new file mode 100644 index 00000000..84f09922 --- /dev/null +++ b/packages/oui-tabs/README.md @@ -0,0 +1,81 @@ +# Textarea + + + +## Usage + +### Basic + +```html:preview + + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet congue urna. Praesent ultricies id ex convallis dictum. Pellentesque malesuada faucibus consectetur. Quisque vehicula tincidunt leo, quis auctor nisi luctus quis. Etiam purus lectus, placerat vitae vehicula at, molestie nec erat. Duis enim odio, maximus at laoreet in, finibus nec mi. Nam ultrices, lacus vitae egestas volutpat, libero odio gravida turpis, vitae dapibus ex mi nec quam. Pellentesque a tempor nibh.

+
+ +

Proin egestas fermentum lectus nec euismod. Vivamus eu congue dui. Pellentesque sit amet pellentesque quam. Morbi posuere sem nec rutrum placerat. Vestibulum porttitor arcu eu risus tempor consectetur. Fusce aliquam bibendum aliquet. Morbi semper egestas iaculis. Ut sit amet sem et neque porta cursus pellentesque nec augue. Nullam semper in metus et luctus. Nunc molestie non ipsum a consequat. Etiam pellentesque laoreet lectus ut luctus. Nulla maximus, leo a mattis gravida, ligula felis vulputate libero, vitae fringilla nibh mauris nec dui. Fusce sed massa at arcu euismod dictum id sit amet lorem. Aliquam sed viverra sem, quis vehicula ligula. Vivamus blandit varius condimentum.

+
+ +

Duis egestas nulla at euismod semper. Nullam bibendum auctor viverra. Sed posuere neque nulla, id cursus nisi molestie vel. Nulla ornare elit sit amet congue faucibus. Aliquam eget lorem id justo ornare pretium in sit amet lectus. Sed maximus odio id porttitor rhoncus. Quisque pulvinar mauris ut sapien dictum, ultrices fermentum orci efficitur. Cras nec auctor ante. Aliquam ornare eleifend neque, at condimentum lacus aliquet elementum. Mauris mattis porttitor tortor vel vehicula. Phasellus venenatis nibh nec viverra sollicitudin. Ut lobortis mattis mauris, vel euismod nibh faucibus a.

+
+
+``` + +### Dynamic + +```html:preview +

+ + {{$ctrl.hideDynamic1 ? 'Show' : 'Hide'}} Dynamic1 tab + + + {{$ctrl.hideDynamic2 ? 'Show' : 'Hide'}} Dynamic2 tab + + + {{$ctrl.hideDynamic3 ? 'Show' : 'Hide'}} Dynamic3 tab + +

+ + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet congue urna. Praesent ultricies id ex convallis dictum. Pellentesque malesuada faucibus consectetur. Quisque vehicula tincidunt leo, quis auctor nisi luctus quis. Etiam purus lectus, placerat vitae vehicula at, molestie nec erat. Duis enim odio, maximus at laoreet in, finibus nec mi. Nam ultrices, lacus vitae egestas volutpat, libero odio gravida turpis, vitae dapibus ex mi nec quam. Pellentesque a tempor nibh.

+
+ +

Proin egestas fermentum lectus nec euismod. Vivamus eu congue dui. Pellentesque sit amet pellentesque quam. Morbi posuere sem nec rutrum placerat. Vestibulum porttitor arcu eu risus tempor consectetur. Fusce aliquam bibendum aliquet. Morbi semper egestas iaculis. Ut sit amet sem et neque porta cursus pellentesque nec augue. Nullam semper in metus et luctus. Nunc molestie non ipsum a consequat. Etiam pellentesque laoreet lectus ut luctus. Nulla maximus, leo a mattis gravida, ligula felis vulputate libero, vitae fringilla nibh mauris nec dui. Fusce sed massa at arcu euismod dictum id sit amet lorem. Aliquam sed viverra sem, quis vehicula ligula. Vivamus blandit varius condimentum.

+
+ +

Duis egestas nulla at euismod semper. Nullam bibendum auctor viverra. Sed posuere neque nulla, id cursus nisi molestie vel. Nulla ornare elit sit amet congue faucibus. Aliquam eget lorem id justo ornare pretium in sit amet lectus. Sed maximus odio id porttitor rhoncus. Quisque pulvinar mauris ut sapien dictum, ultrices fermentum orci efficitur. Cras nec auctor ante. Aliquam ornare eleifend neque, at condimentum lacus aliquet elementum. Mauris mattis porttitor tortor vel vehicula. Phasellus venenatis nibh nec viverra sollicitudin. Ut lobortis mattis mauris, vel euismod nibh faucibus a.

+
+
+``` + +### Check marks + +```html:preview + + + Toggle check mark of tab 1 + + + Toggle check mark of tab 2 + + + Toggle check mark of tab 3 + + +``` + +## API + +### oui-tabs-item + +| Attribute | Type | Binding | One-time Binding | Values | Default | Description +| ---- | ---- | ---- | ---- | ---- | ---- | ---- +| `aria-label` | string | @? | yes | n/a | n/a | accessibility label + +### oui-tabs-item + +| Attribute | Type | Binding | One-time Binding | Values | Default | Description +| ---- | ---- | ---- | ---- | ---- | ---- | ---- +| `id` | string | @? | yes | n/a | n/a | id attribute of the panel +| `heading` | string | @? | yes | n/a | n/a | heading text of the tab +| `aria-label` | string | @? | yes | n/a | n/a | accessibility label +| `checked` | booldean | { + let TestUtils; + let $timeout; + + beforeEach(angular.mock.module("oui.tabs")); + beforeEach(angular.mock.module("oui.test-utils")); + + beforeEach(inject((_TestUtils_, _$timeout_) => { + TestUtils = _TestUtils_; + $timeout = _$timeout_; + })); + + describe("Components", () => { + it("should add default classname", () => { + const element = TestUtils.compileTemplate(` + + + `); + + $timeout.flush(); + + expect(element.hasClass("oui-tabs")).toBeTruthy(); + expect(element.find("oui-tabs-item").hasClass("oui-tabs-item")).toBeTruthy(); + }); + + it("should have accessibility attribute", () => { + const element = TestUtils.compileTemplate(` + + + `); + + $timeout.flush(); + + const content = angular.element(element[0].querySelector(".oui-tabs-item__content")); + const contentId = content.attr("id"); + expect(content.attr("role")).toBe("tabpanel"); + expect(content.attr("aria-hidden")).toBeDefined(); + + const tablist = angular.element(element[0].querySelector(".oui-tabs__tablist")); + expect(tablist.attr("role")).toBe("tablist"); + expect(tablist.attr("aria-label")).toBe("tablist"); + + const tab = angular.element(element[0].querySelector(".oui-tabs__tab")); + expect(tab.attr("aria-label")).toBe("heading"); + expect(tab.attr("aria-controls")).toBe(contentId); + expect(tab.attr("aria-selected")).toBeDefined(); + + const button = angular.element(element[0].querySelector(".oui-tabs-item__button")); + expect(button.attr("aria-label")).toBe("heading"); + expect(button.attr("aria-controls")).toBe(contentId); + expect(button.attr("aria-expanded")).toBeDefined(); + }); + + it("should set tabs with heading", () => { + const element = TestUtils.compileTemplate(` + + + + `); + + $timeout.flush(); + + const items = element.find("oui-tabs-item"); + const tabs = angular.element(element[0].querySelector(".oui-tabs__tablist")).find("button"); + expect(tabs.length).toBe(items.length); + + angular.forEach(items, (item, index) => { + const heading = angular.element(item).attr("heading"); + const tab = angular.element(tabs[index]); + expect(tab.text().trim()).toBe(heading); + }); + }); + + it("should update active tab", () => { + const element = TestUtils.compileTemplate(` + + + + `); + + $timeout.flush(); + + const items = element.find("oui-tabs-item"); + const button = angular.element(items[1]).find("button"); + button.triggerHandler("click"); + + const id = button.next().attr("id"); + const tabsCtrl = element.controller("ouiTabs"); + expect(tabsCtrl.activeId).toBe(id); + }); + + it("should update checkmark", () => { + const checked = [true, false]; + const element = TestUtils.compileTemplate(` + + + + `); + + $timeout.flush(); + + const items = element.find("oui-tabs-item"); + angular.forEach(items, (item, index) => { + const heading = angular.element(item).find("button"); + expect(heading.hasClass("oui-tabs-item__button_checked")).toBe(checked[index]); + }); + }); + }); + + describe("Methods", () => { + it("should add item in items array", () => { + const element = TestUtils.compileTemplate(""); + const tabsCtrl = element.controller("ouiTabs"); + + tabsCtrl.items = ["ipsum"]; + tabsCtrl.addItem("lorem", 0); // Should be added at index 0 + tabsCtrl.addItem("dolor"); // Should be added at the end + + expect(tabsCtrl.items.indexOf("lorem")).toBe(0); + expect(tabsCtrl.items.indexOf("dolor")).toBe(tabsCtrl.items.length - 1); + }); + + it("should remove item in items array", () => { + const element = TestUtils.compileTemplate(""); + const tabsCtrl = element.controller("ouiTabs"); + + tabsCtrl.items = ["lorem", "ipsum", "dolor"]; + tabsCtrl.removeItem("ipsum"); + + expect(tabsCtrl.items.indexOf("ipsum")).toBe(-1); + }); + }); +}); diff --git a/packages/oui-tabs/src/item/tabs-item.component.js b/packages/oui-tabs/src/item/tabs-item.component.js new file mode 100644 index 00000000..0209960f --- /dev/null +++ b/packages/oui-tabs/src/item/tabs-item.component.js @@ -0,0 +1,17 @@ +import controller from "./tabs-item.controller"; +import template from "./tabs-item.html"; + +export default { + require: { + tabsCtrl: "^ouiTabs" + }, + bindings: { + id: "@?", + heading: "@?", + ariaLabel: "@?", + checked: " this.tabsCtrl.activeId, + (value) => { + this.isHidden = value !== this.id; + } + ); + } + + $onDestroy () { + if (this.tabsCtrl) { + this.tabsCtrl.removeItem(this); + } + } + + $postLink () { + this.$timeout(() => + this.$element.addClass("oui-tabs-item") + ); + } +} diff --git a/packages/oui-tabs/src/item/tabs-item.html b/packages/oui-tabs/src/item/tabs-item.html new file mode 100644 index 00000000..17db8856 --- /dev/null +++ b/packages/oui-tabs/src/item/tabs-item.html @@ -0,0 +1,19 @@ + +
+
diff --git a/packages/oui-tabs/src/tabs.component.js b/packages/oui-tabs/src/tabs.component.js new file mode 100644 index 00000000..827c19f3 --- /dev/null +++ b/packages/oui-tabs/src/tabs.component.js @@ -0,0 +1,13 @@ +import controller from "./tabs.controller"; +import template from "./tabs.html"; + +export default { + controller, + template, + bindings: { + ariaLabel: "@?" + }, + transclude: { + item: "?ouiTabsItem" + } +}; diff --git a/packages/oui-tabs/src/tabs.controller.js b/packages/oui-tabs/src/tabs.controller.js new file mode 100644 index 00000000..a4291f33 --- /dev/null +++ b/packages/oui-tabs/src/tabs.controller.js @@ -0,0 +1,51 @@ +export default class { + constructor ($element, $timeout) { + "ngInject"; + + this.$element = $element; + this.$timeout = $timeout; + } + + addItem (item, index) { + // Position item in items array + if (angular.isNumber(index)) { + this.items.splice(index, 0, item); + } else { + this.items.push(item); + } + + // Set first added tab active + if (this.items.length === 1) { + this.setActiveTab(item.id); + } + } + + removeItem (item) { + const index = this.items.indexOf(item); + + if (index > -1) { + this.items.splice(index, 1); + } + + // If was activeId, set first item as active + if (this.items.length && item.id === this.activeId) { + this.setActiveTab(this.items[0].id); + } + } + + setActiveTab (id) { + this.activeId = id; + } + + $onInit () { + this.items = []; + } + + $postLink () { + this.$timeout(() => + this.$element + .addClass("oui-tabs") + .removeAttr("aria-label") + ); + } +} diff --git a/packages/oui-tabs/src/tabs.html b/packages/oui-tabs/src/tabs.html new file mode 100644 index 00000000..c9def067 --- /dev/null +++ b/packages/oui-tabs/src/tabs.html @@ -0,0 +1,18 @@ +
+ +
+
+
From 5f63458e6f60354208a1cecb6437b311213799c4 Mon Sep 17 00:00:00 2001 From: Axel Peter Date: Fri, 16 Nov 2018 16:24:04 +0100 Subject: [PATCH 2/5] docs(oui-tabs): update documentation --- packages/oui-tabs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/oui-tabs/README.md b/packages/oui-tabs/README.md index 84f09922..de464713 100644 --- a/packages/oui-tabs/README.md +++ b/packages/oui-tabs/README.md @@ -1,6 +1,6 @@ -# Textarea +# Tabs - + ## Usage From bdaa6b169caf0e107a021f7926084b8d0bcf65ec Mon Sep 17 00:00:00 2001 From: Axel Peter Date: Mon, 19 Nov 2018 16:01:38 +0100 Subject: [PATCH 3/5] style(oui-tabs): code review --- packages/oui-tabs/src/item/tabs-item.controller.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/oui-tabs/src/item/tabs-item.controller.js b/packages/oui-tabs/src/item/tabs-item.controller.js index 8adbf394..3609118d 100644 --- a/packages/oui-tabs/src/item/tabs-item.controller.js +++ b/packages/oui-tabs/src/item/tabs-item.controller.js @@ -35,12 +35,6 @@ export default class { this.hasCheckmark = angular.isDefined(this.$attrs.checked); - if (this.tabsCtrl) { - // Add item to parent oui-tabs and give node index - // To support ngIf directive, which delay the adding - this.tabsCtrl.addItem(this, this.getNodeIndex()); - } - // Watch if hidden this.$scope.$watch( () => this.tabsCtrl.activeId, @@ -60,5 +54,11 @@ export default class { this.$timeout(() => this.$element.addClass("oui-tabs-item") ); + + if (this.tabsCtrl) { + // Add item to parent oui-tabs and give node index + // To support ngIf directive, which delay the adding + this.tabsCtrl.addItem(this, this.getNodeIndex()); + } } } From 9ada07cb0d70a24f6d776d8cd688e386f7e872d0 Mon Sep 17 00:00:00 2001 From: Marie JONES <14836007+marie-j@users.noreply.github.com> Date: Mon, 19 Nov 2018 17:05:09 +0100 Subject: [PATCH 4/5] style(oui-tabs): code review Co-Authored-By: AxelPeter <15101925+AxelPeter@users.noreply.github.com> --- packages/oui-tabs/src/index.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/oui-tabs/src/index.spec.js b/packages/oui-tabs/src/index.spec.js index f0e922b3..8b07de8e 100644 --- a/packages/oui-tabs/src/index.spec.js +++ b/packages/oui-tabs/src/index.spec.js @@ -1,4 +1,4 @@ -describe("ouiSwitch", () => { +describe("ouiTabs", () => { let TestUtils; let $timeout; From bdb1f57d4f60e0ba466c164d3f2677f9d1a3aa85 Mon Sep 17 00:00:00 2001 From: Antoine Leblanc Date: Tue, 20 Nov 2018 09:58:31 +0100 Subject: [PATCH 5/5] docs(oui-tabs): update documentation Co-Authored-By: AxelPeter <15101925+AxelPeter@users.noreply.github.com> --- packages/oui-tabs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/oui-tabs/README.md b/packages/oui-tabs/README.md index de464713..260607c1 100644 --- a/packages/oui-tabs/README.md +++ b/packages/oui-tabs/README.md @@ -65,7 +65,7 @@ ## API -### oui-tabs-item +### oui-tabs | Attribute | Type | Binding | One-time Binding | Values | Default | Description | ---- | ---- | ---- | ---- | ---- | ---- | ----