diff --git a/packages/oui-header-tabs/README.md b/packages/oui-header-tabs/README.md
index 8abb0d32..7e2d249c 100644
--- a/packages/oui-header-tabs/README.md
+++ b/packages/oui-header-tabs/README.md
@@ -92,7 +92,7 @@
| ---- | ---- | ---- | ---- | ---- | ---- | ----
| `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
+| `state` | string | @? | yes | | | state of the menu item
| `stateParams` | object | | | | | state params of the menu item
| `active` | boolean | | | `true`, `false` | `false` | manual active flag
| `disabled` | boolean | | yes | `true`, `false` | `false` | disabled flag
diff --git a/packages/oui-navbar/README.md b/packages/oui-navbar/README.md
index 2670d2d3..eb8f3eba 100644
--- a/packages/oui-navbar/README.md
+++ b/packages/oui-navbar/README.md
@@ -4,8 +4,9 @@
## Usage
+### Basic
+
```html:preview
-
-
```
-Note: All children menus have `.oui-navbar-menu_fixed`. The component is intended to be used in `fixed` mode. To avoid being hidden by the documentation navbar, this example is not `fixed`.
+### Advanced
-## API
+```html:preview
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Brand
-| Attribute | Type | Binding | One-time Binding | Values | Default | Description |
-| ---- | ---- | ---- | ---- | ---- | ---- | ---- |
-| brand | object | | true | _see example below_ | | object for the brand logo of the navbar |
-| active-link | string | @? | true | | | current active-link of the navbar |
-| main-links | array | | true | _see example below_ | | array of objects for the items on the left and the toggler (for responsive) |
-| aside-links | array | | true | _see example below_ | | array of objects for the items on the right |
-| toggler-links | array | | true | _see example below_ | | array of objects for the responsive menu |
-| fixed | boolean | | true | | false | set the navbar in fixed mode |
+### With attribute `brand`
+
+```html:preview
+
+
+```
### Properties of attribute `brand`
-- `label` _(optional)_: define `aria-label` of the brand link.
-- `title`: _(optional)_: define the brand text.
+- `label` **(optional)**: define `aria-label` of the brand link.
+- `title`: **(optional)**: define the brand text.
- `url`: define `href` of the brand link.
#### Set a brand icon with a CSS class (for `oui-icon`)
@@ -41,8 +101,6 @@ Note: All children menus have `.oui-navbar-menu_fixed`. The component is intende
The brand icon will be set as a ``.
-###### Example
-
```json
{
"label": String,
@@ -54,14 +112,12 @@ The brand icon will be set as a ``.
#### Set a brand icon with an image
-- `iconAlt` _(optional)_: define `alt` of the brand icon.
-- `iconClass` _(optional)_: define `class` of the brand icon.
+- `iconAlt` **(optional)**: define `alt` of the brand icon.
+- `iconClass` **(optional)**: define `class` of the brand icon.
- `iconSrc`: define `src` of the brand icon.
The brand icon will be set as a ` `.
-###### Example
-
```json
{
"label": String,
@@ -73,17 +129,30 @@ The brand icon will be set as a ` `.
}
```
+### With component `oui-navbar-brand`
+
+```html:preview
+
+
+
+```
+
+## Links
+
### Common properties of attributes `*-links`
- `name`: define the navigation name of a menu.
-- `class` _(optional)_: define `class` of the menu item (only used for root links).
-- `label` _(optional)_: define `aria-label` of the menu item.
+- `class` **(optional)**: define `class` of the menu item (only used for root links).
+- `label` **(optional)**: define `aria-label` of the menu item.
- `title`: define the menu item text.
-- `headerTitle` _(optional)_: define the title of the menu header (default text is `title`).
-- `headerBreadcrumb` _(optional)_: define the breadcrumb of the menu header.
-- `headerTemplate` _(optional)_: define the HTML template of the menu header.
-- `isActive` _(optional)_: define if the menu item has active variant `.oui-navbar-menu__item_active`.
-- `acknowledged` _(optional)_: define if the menu item is acknowledged.
+- `headerTitle` **(optional)**: define the title of the menu header (default text is `title`).
+- `headerBreadcrumb` **(optional)**: define the breadcrumb of the menu header.
+- `headerTemplate` **(optional)**: define the HTML template of the menu header.
+- `isActive` **(optional)**: define if the menu item has active variant `.oui-navbar-menu__item_active`.
+- `acknowledged` **(optional)**: define if the menu item is acknowledged.
If `headerTemplate` is defined, `headerBreadcrumb` and `headerTitle` are not used.
@@ -93,8 +162,6 @@ If `headerTemplate` is defined, `headerBreadcrumb` and `headerTitle` are not use
The menu item will be set as a ``.
-##### Example
-
```json
{
"name": String,
@@ -108,12 +175,10 @@ The menu item will be set as a ` `.
#### Set a menu item as a link for ui-router
- `state`: define `ui-sref` of the menu item. The menu item will be set as a ` `, `click` and `url` will be ignored.
-- `stateParams` _(optional)_: define parameters for `state`.
+- `stateParams` **(optional)**: define parameters for `state`.
The menu item will be set as a ` `.
-##### Example
-
```json
{
"name": String,
@@ -131,8 +196,6 @@ The menu item will be set as a ` `.
The menu item will be set as a ``.
-##### Example
-
```json
{
"name": String,
@@ -149,8 +212,6 @@ The menu item will be set as a ``.
The menu item will be set as a ``.
-##### Example
-
```json
{
"name": String,
@@ -163,16 +224,25 @@ The menu item will be set as a ``.
}
```
-Note: Root links can have `url` and `subLinks`. `subLinks` will only be used for the responsive menu.
+**Note**: Root links can have `url` and `subLinks`. `subLinks` will only be used for the responsive menu.
+
+## Main Links
+
+### With attribute `main-links`
+
+```html:preview
+
+
+```
-### Properties of attribute `main-links`
+#### Properties of attribute `main-links`
This property is only available for root links of `main-links`.
- `isPrimary`: define if the menu item has primary variant `.oui-navbar-link_primary`, else has secondary variant `.oui-navbar-link_secondary`.
-##### Example
-
```json
[{
"name": String,
@@ -188,14 +258,68 @@ This property is only available for root links of `main-links`.
}]
```
-### Properties of attribute `aside-links`
+### With component `oui-navbar-main`
+
+```html:preview
+
+
+
+
+
+```
+
+## Toggler Links
+
+It defines the menu in reponsive mode. It will be visible only for screen resolutions below `1024px` of width.
+
+### With attributes `toggler-links`
+
+```html:preview
+
+
+```
+
+**Note**: If there is no `toggler-links` attribute, it will use `main-links` instead to build the toggler menu.
+
+```html:preview
+
+
+```
+
+### With component `oui-navbar-toggler`
+
+```html:preview
+
+
+
+```
+
+## Aside Links
+
+### With attribute `aside-links`
+
+```html:preview
+
+
+```
+
+#### Properties of attribute `aside-links`
This property is only available for root links of `aside-links`.
- `iconClass`: define `class` of the menu item icon.
-##### Example
-
```json
[{
"name": String,
@@ -211,44 +335,67 @@ This property is only available for root links of `aside-links`.
}]
```
-#### Variant `notifications` menu
+### With component `oui-navbar-aside`
+
+```html:preview
+
+
+
+
+
+
+
+
+
+```
+
+## Variant `notifications` menu
The property `name` **must be** `"notifications"`.
- `template`: define the HTML template in the menu item.
-- `limitTo` _(optional)_: define the maximum of menu links displayed.
+- `limitTo` **(optional)**: define the maximum of menu links displayed.
- `footerTitle`: define the text of the link in the menu footer.
- `footerUrl`: define `href` of the link in the menu footer.
-- `footerTemplate` _(optional)_: define the HTML template of the menu footer.
+- `footerTemplate` **(optional)**: define the HTML template of the menu footer.
If `footerTemplate` is defined, `footerTitle` and `footerUrl` are not used.
-#### Placeholder for notification
-In the notification submenu, a placeholder is shown depending if there's no notification or if variable `subLinks` is not defined inside an `aside-links`.
-```javascript
-// Something went wong with the notification
-subLinks = undefined;
-// No notification
-subLinks = [];
-```
-```html:preview
-
-
-
-
-
-
-
-
-```
-
-
-##### Example
-
```json
{
"name": "notifications",
@@ -274,15 +421,54 @@ subLinks = [];
}
```
-#### Variant `user` menu
+### Placeholder for notification
+In the notification submenu, a placeholder is shown depending if there's no notification or if variable `subLinks` is not defined inside an `aside-links`.
+```javascript
+// Something went wong with the notification
+subLinks = undefined;
+// No notification
+subLinks = [];
+```
+
+```html:preview
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Variant `user` menu
The property `name` **must be** `"user"`.
- `nichandle`: Define the nichandle in the menu header.
- `fullName`: Define the fullname in the menu header.
-##### Example
-
```json
{
"name": "user",
@@ -299,3 +485,84 @@ The property `name` **must be** `"user"`.
}]
}
```
+
+## API
+
+### oui-navbar
+
+| Attribute | Type | Binding | One-time Binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `brand` | object | | yes | _see example above_ | | object for the brand logo of the navbar
+| `active-link` | string | @? | yes | | | current active-link of the navbar
+| `main-links` | array | | yes | _see example above_ | | array of objects for the items on the left and the toggler (for responsive)
+| `aside-links` | array | | yes | _see example above_ | | array of objects for the items on the right
+| `toggler-links` | array | | yes | _see example above_ | | array of objects for the responsive menu
+| `fixed` | boolean | | yes | | `false` | set the navbar in position fixed
+
+### oui-navbar-brand
+
+| Attribute | Type | Binding | One-time Binding | Values | Default | Description
+| ---- | ---- | | ---- | ---- | ---- | ----
+| `heading` | string | @? | yes | | | title of the brand link
+| `aria-label` | string | @? | yes | | | accessibility label of the brand link
+| `icon-alt` | string | @? | yes | | | alternative text of the brand icon
+| `icon-class` | string | @? | yes | | | classname of the brand icon
+| `icon-src` | string | @? | yes | | | url of the brand icon
+| `href` | string | @? | yes | | | link href of the brand link
+
+### oui-navbar-dropdown
+
+| Attribute | Type | Binding | One-time Binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `name` | string | @ | yes | | | group name of the dropdown
+| `text` | string | @ | yes | | | text of the button
+| `aria-label` | string | @? | yes | | | accessibility label of the button
+| `icon-class` | string | @? | yes | | | classname of the button icon
+| `icon-badge` | number | | | | | number on the badge of the button icon
+| `on-click` | function | & | | | | click callback
+
+### oui-navbar-menu
+
+| Attribute | Type | Binding | One-time Binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `name` | string | @ | yes | | | group name of the menu
+| `links` | array | < | yes | _see example above_ | | array of links for the menu
+| `header-class` | string | @? | yes | | | classname of the header
+| `header-title` | string | @? | yes | | | text of the header title
+| `header-breadcrumb` | string | @? | yes | | | text of the header breadcrumb
+| `align` | string | @? | yes | `start`, `end` | `start` | alignment of the menu to his trigger
+| `back-button` | boolean | | yes | | `false` | display a back button in the header title
+| `fixed` | boolean | | yes | | `false` | flag for responsive menu
+
+### oui-navbar-notification
+
+| Attribute | Type | Binding | One-time Binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `name` | string | @ | yes | | | group name of the menu
+| `links` | array | < | yes | _see example above_ | | array of links for the notifications menu
+| `limit-to` | number | | yes | | 10 | maximum displayed notifications
+| `header-template` | string | | yes | | | HTML template of the menu header
+| `header-title` | string | @? | yes | | | text of the header title
+| `footer-template` | string | | yes | | | HTML template of the menu footer
+| `footer-title` | string | @? | yes | | | text of the footer link
+| `footer-href` | string | @? | yes | | | url of the footer link
+| `align` | string | @? | yes | `start`, `end` | `start` | alignment of the menu to his trigger
+| `fixed` | boolean | | yes | | `false` | flag for responsive menu
+
+### oui-navbar-toggler
+
+| Attribute | Type | Binding | One-time Binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `links` | array | < | yes | _see example above_ | | array of links for the responsive menu
+
+### oui-navbar-link
+
+| Attribute | Type | Binding | One-time Binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `name` | string | @ | yes | | | group name of the link
+| `text` | string | @ | yes | | | text of the link
+| `aria-label` | string | @? | yes | | | accessibility label of the link
+| `href` | string | @? | yes | | | href of the link
+| `state` | string | @? | yes | | | state of the link
+| `state-params` | object | | yes | | | state-params of the link
+| `variant` | string | @? | yes | `primary`, `secondary`, `tertiary` | | style modifier of the link
diff --git a/packages/oui-navbar/src/brand/navbar-brand.component.js b/packages/oui-navbar/src/brand/navbar-brand.component.js
new file mode 100644
index 00000000..cc4a3a4e
--- /dev/null
+++ b/packages/oui-navbar/src/brand/navbar-brand.component.js
@@ -0,0 +1,28 @@
+import template from "./navbar-brand.html";
+
+export default {
+ bindings: {
+ heading: "@?",
+ ariaLabel: "@?",
+ iconAlt: "@?",
+ iconClass: "@?",
+ iconSrc: "@?",
+ href: "@?"
+ },
+ controller: class {
+ constructor ($element, $timeout) {
+ "ngInject";
+
+ this.$element = $element;
+ this.$timeout = $timeout;
+ }
+
+ $postLink () {
+ this.$timeout(() =>
+ this.$element
+ .removeAttr("aria-label")
+ );
+ }
+ },
+ template
+};
diff --git a/packages/oui-navbar/src/brand/navbar-brand.html b/packages/oui-navbar/src/brand/navbar-brand.html
new file mode 100644
index 00000000..033841cb
--- /dev/null
+++ b/packages/oui-navbar/src/brand/navbar-brand.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/packages/oui-navbar/src/dropdown/menu/navbar-dropdown-menu.component.js b/packages/oui-navbar/src/dropdown/menu/navbar-dropdown-menu.component.js
new file mode 100644
index 00000000..eb2c87f3
--- /dev/null
+++ b/packages/oui-navbar/src/dropdown/menu/navbar-dropdown-menu.component.js
@@ -0,0 +1,25 @@
+export default {
+ bindings: {
+ align: "@?"
+ },
+ controller: class {
+ constructor ($element, $timeout) {
+ "ngInject";
+
+ this.$element = $element;
+ this.$timeout = $timeout;
+ }
+
+ $postLink () {
+ this.$timeout(() => {
+ this.$element
+ .addClass("oui-navbar-menu")
+ .addClass("oui-navbar-menu_fixed");
+
+ if (this.align) {
+ this.$element.addClass(`oui-navbar-menu_${this.align}`);
+ }
+ });
+ }
+ }
+};
diff --git a/packages/oui-navbar/src/dropdown/navbar-dropdown.component.js b/packages/oui-navbar/src/dropdown/navbar-dropdown.component.js
new file mode 100644
index 00000000..13d1f672
--- /dev/null
+++ b/packages/oui-navbar/src/dropdown/navbar-dropdown.component.js
@@ -0,0 +1,19 @@
+import controller from "./navbar-dropdown.controller";
+import template from "./navbar-dropdown.html";
+
+export default {
+ require: {
+ navbarCtrl: "^^ouiNavbar"
+ },
+ bindings: {
+ name: "@",
+ text: "@",
+ label: "@?ariaLabel",
+ iconBadge: "",
+ iconClass: "@?",
+ onClick: "&"
+ },
+ controller,
+ template,
+ transclude: true
+};
diff --git a/packages/oui-navbar/src/dropdown/navbar-dropdown.controller.js b/packages/oui-navbar/src/dropdown/navbar-dropdown.controller.js
new file mode 100644
index 00000000..1d3a1488
--- /dev/null
+++ b/packages/oui-navbar/src/dropdown/navbar-dropdown.controller.js
@@ -0,0 +1,16 @@
+export default class {
+ constructor ($element, $timeout) {
+ "ngInject";
+
+ this.$element = $element;
+ this.$timeout = $timeout;
+ }
+
+ $postLink () {
+ this.$timeout(() =>
+ this.$element
+ .addClass("oui-navbar-dropdown")
+ .addClass("oui-navbar-list__item")
+ );
+ }
+}
diff --git a/packages/oui-navbar/src/dropdown/navbar-dropdown.html b/packages/oui-navbar/src/dropdown/navbar-dropdown.html
new file mode 100644
index 00000000..117b07fe
--- /dev/null
+++ b/packages/oui-navbar/src/dropdown/navbar-dropdown.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/packages/oui-navbar/src/group/navbar-group.controller.js b/packages/oui-navbar/src/group/navbar-group.controller.js
index f603a64e..176afce1 100644
--- a/packages/oui-navbar/src/group/navbar-group.controller.js
+++ b/packages/oui-navbar/src/group/navbar-group.controller.js
@@ -1,11 +1,57 @@
export default class {
- constructor ($attrs, $element, ouiNavbarConfiguration, NavbarGroupService) {
+ constructor ($attrs, $element, ouiNavbarConfiguration, KEYBOARD_KEYS) {
"ngInject";
this.$attrs = $attrs;
this.$element = $element;
this.config = ouiNavbarConfiguration;
- this.navbarGroupService = NavbarGroupService;
+
+ this.KEYBOARD_KEYS = KEYBOARD_KEYS;
+ }
+
+ bindGroup (groupName) {
+ const keys = {};
+ const keysRegex = new RegExp([
+ this.KEYBOARD_KEYS.TAB,
+ this.KEYBOARD_KEYS.SHIFT
+ ].join("|"));
+
+ const tabbableItems = this.navbarCtrl.getGroup(groupName);
+ const lastIndex = tabbableItems.length - 1;
+ const focusElement = (e, groupIndex) => {
+ let index = groupIndex;
+ keys[e.which] = true;
+
+ if (keys[this.KEYBOARD_KEYS.TAB] && !keys[this.KEYBOARD_KEYS.SHIFT]) {
+ // Move Down
+ index = index >= lastIndex ? 0 : index + 1;
+ } else if (keys[this.KEYBOARD_KEYS.TAB] && keys[this.KEYBOARD_KEYS.SHIFT]) {
+ // Move Up
+ index = index <= 0 ? lastIndex : index - 1;
+ }
+
+ // Check if element is visible
+ if (tabbableItems[index].clientHeight) {
+ tabbableItems[index].focus();
+ } else {
+ focusElement(e, index);
+ }
+ };
+
+ angular.element(tabbableItems)
+ .on("keydown", (e) => {
+ if (keysRegex.test(e.which) && this.isOpen(groupName)) {
+ e.preventDefault();
+ focusElement(e, this.navbarCtrl.getGroup(groupName).indexOf(e.target));
+ }
+ })
+ .on("keyup", (e) => {
+ delete keys[e.which];
+ });
+ }
+
+ isOpen (state) {
+ return this.navbarCtrl.navigation && this.navbarCtrl.navigation[state];
}
$onInit () {
@@ -16,11 +62,11 @@ export default class {
}
$postLink () {
- this.navbarGroupService.addItemToGroup(this.$element[0], this.groupName);
+ this.navbarCtrl.addItemToGroup(this.$element[0], this.groupName);
// Bind items when it's the last item
if (this.isLast) {
- this.navbarGroupService.bindGroup(this.groupName);
+ this.bindGroup(this.groupName);
}
}
}
diff --git a/packages/oui-navbar/src/group/navbar-group.directive.js b/packages/oui-navbar/src/group/navbar-group.directive.js
index 54cdbee9..d476a6e2 100644
--- a/packages/oui-navbar/src/group/navbar-group.directive.js
+++ b/packages/oui-navbar/src/group/navbar-group.directive.js
@@ -2,6 +2,9 @@ import controller from "./navbar-group.controller";
export default () => ({
restrict: "A",
+ require: {
+ navbarCtrl: "^ouiNavbar"
+ },
bindToController: {
groupName: "@ouiNavbarGroup",
isLast: " {
- let index = groupIndex;
- keys[e.which] = true;
-
- if (keys[this.KEYBOARD_KEYS.ALT] && !keys[this.KEYBOARD_KEYS.TAB]) {
- // Move Down
- index = index >= lastIndex ? 0 : index + 1;
- } else if (keys[this.KEYBOARD_KEYS.ALT] && keys[this.KEYBOARD_KEYS.TAB]) {
- // Move Up
- index = index <= 0 ? lastIndex : index - 1;
- }
-
- // Check if element is visible
- if (tabbableItems[index].clientHeight) {
- tabbableItems[index].focus();
- } else {
- focusElement(e, index);
- }
- };
-
- angular.element(tabbableItems)
- .on("keydown", (e) => {
- if (keysRegex.test(e.which) && this.navbarService.isOpen(groupName)) {
- e.preventDefault();
- focusElement(e, this.keyboardNav[groupName].indexOf(e.target));
- }
- })
- .on("keyup", (e) => {
- delete keys[e.which];
- });
- }
-
- // Set focus to an item of a group
- setFocusTo (groupName, index) {
- // Add a delay to force focus
- const delay = 50;
- this.$timeout(() => this.keyboardNav[groupName][index] && this.keyboardNav[groupName][index].focus(), delay);
- }
-}
diff --git a/packages/oui-navbar/src/index.js b/packages/oui-navbar/src/index.js
index abddba4c..99b858bc 100644
--- a/packages/oui-navbar/src/index.js
+++ b/packages/oui-navbar/src/index.js
@@ -1,10 +1,15 @@
import KEYBOARD_KEYS from "./keyboard-keys.constant";
+
import Navbar from "./navbar.component";
-import NavbarConfigurationProvider from "./navbar.provider.js";
+import NavbarBrand from "./brand/navbar-brand.component";
+import NavbarConfigurationProvider from "./navbar.provider";
+import NavbarDropdown from "./dropdown/navbar-dropdown.component";
+import NavbarDropdownMenu from "./dropdown/menu/navbar-dropdown-menu.component";
import NavbarGroup from "./group/navbar-group.directive";
-import NavbarGroupService from "./group/navbar-group.service";
+import NavbarLink from "./link/navbar-link.component";
import NavbarMenu from "./menu/navbar-menu.component";
-import NavbarService from "./navbar.service";
+import NavbarNotification from "./notification/navbar-notification.component";
+import NavbarToggler from "./toggler/navbar-toggler.component";
export default angular
.module("oui.navbar", [
@@ -13,9 +18,13 @@ export default angular
])
.constant("KEYBOARD_KEYS", KEYBOARD_KEYS)
.component("ouiNavbar", Navbar)
- .directive("ouiNavbarGroup", NavbarGroup)
+ .component("ouiNavbarBrand", NavbarBrand)
+ .component("ouiNavbarDropdown", NavbarDropdown)
+ .component("ouiNavbarDropdownMenu", NavbarDropdownMenu)
+ .component("ouiNavbarLink", NavbarLink)
.component("ouiNavbarMenu", NavbarMenu)
+ .component("ouiNavbarNotification", NavbarNotification)
+ .component("ouiNavbarToggler", NavbarToggler)
+ .directive("ouiNavbarGroup", NavbarGroup)
.provider("ouiNavbarConfiguration", NavbarConfigurationProvider)
- .service("NavbarService", NavbarService)
- .service("NavbarGroupService", NavbarGroupService)
.name;
diff --git a/packages/oui-navbar/src/index.spec.js b/packages/oui-navbar/src/index.spec.js
index 8fd210be..c3e67f15 100644
--- a/packages/oui-navbar/src/index.spec.js
+++ b/packages/oui-navbar/src/index.spec.js
@@ -2,29 +2,255 @@ import mockData from "./index.spec.data.json";
describe("ouiNavbar", () => {
let testUtils;
+ let $document;
+ let $timeout;
+ let configuration;
beforeEach(angular.mock.module("oui.navbar"));
+ beforeEach(angular.mock.module("oui.navbar.configuration"));
beforeEach(angular.mock.module("oui.test-utils"));
- beforeEach(inject((_TestUtils_) => {
+ beforeEach(inject((_$document_, _$timeout_, _TestUtils_) => {
+ $document = _$document_;
+ $timeout = _$timeout_;
testUtils = _TestUtils_;
}));
+ describe("Provider", () => {
+ angular.module("oui.navbar.configuration", [
+ "oui.navbar"
+ ]).config(ouiNavbarConfigurationProvider => {
+ ouiNavbarConfigurationProvider.setTranslations({
+ foo: "bar"
+ });
+ });
+
+ beforeEach(inject(_ouiNavbarConfiguration_ => {
+ configuration = _ouiNavbarConfiguration_;
+ }));
+
+ it("should have custom options", () => {
+ expect(configuration.translations.foo).toEqual("bar");
+ });
+ });
+
describe("Component", () => {
+ const navbarClass = "oui-navbar";
+ const navbarFixedClass = `${navbarClass}_fixed`;
+ const navbarDropdownClass = `${navbarClass}-dropdown`;
+ const navbarMenuClass = `${navbarClass}-menu`;
+ const navbarMenuFixedClass = `${navbarMenuClass}_fixed`;
+ const navbarMenuEndClass = `${navbarMenuClass}_end`;
+ const navbarListItemClass = `${navbarClass}-list__item`;
+
+ it("should display a navbar", () => {
+ const component = testUtils.compileTemplate(`
+
+ `, {
+ brand: mockData.brand,
+ activeLink: mockData.mainLinks[0].name,
+ mainLinks: mockData.mainLinks,
+ asideLinks: mockData.asideLinks,
+ togglerLinks: mockData.togglerLinks
+ });
+ const controller = component.controller("ouiNavbar");
+
+ $timeout.flush();
+
+ expect(angular.copy(controller.brand)).toEqual(mockData.brand);
+ expect(angular.copy(controller.activeLink)).toEqual(mockData.mainLinks[0].name);
+ expect(angular.copy(controller.mainLinks)).toEqual(mockData.mainLinks);
+ expect(angular.copy(controller.asideLinks)).toEqual(mockData.asideLinks);
+ expect(angular.copy(controller.togglerLinks)).toEqual(mockData.togglerLinks);
+ });
+
+ describe("Navbar", () => {
+ const item = "foo";
+ const groupName = "bar";
+ const menuName = "lorem";
+ const internalMenuName = "ipsum";
+
+ let component;
+ let controller;
+
+ beforeEach(() => {
+ component = testUtils.compileTemplate(" ");
+ controller = component.controller("ouiNavbar");
+
+ $timeout.flush();
+ });
+
+ it("should have default classname", () => {
+ expect(component.hasClass(navbarClass)).toBeTruthy();
+ expect(component.hasClass(navbarFixedClass)).toBeFalsy();
+ });
+
+ it("should have role 'navigation'", () => {
+ expect(component.attr("role")).toBe("navigation");
+ });
+
+ it("should add item 'foo' to group 'bar'", () => {
+ controller.addItemToGroup(item, groupName);
+
+ expect(Array.isArray(controller.keyboardNav[groupName])).toBeTruthy();
+ expect(controller.keyboardNav[groupName].length).toBe(1);
+ expect(controller.keyboardNav[groupName][0]).toBe(item);
+ });
+
+ it("should return group 'bar'", () => {
+ controller.addItemToGroup(item, groupName);
+ const group = controller.getGroup(groupName);
+
+ expect(Array.isArray(group)).toBeTruthy();
+ expect(group.length).toBe(1);
+ expect(group[0]).toBe(item);
+ });
+
+ it("should toggle navigation state of Menu 'lorem' ", () => {
+ expect(controller.navigation).toBeUndefined();
+
+ controller.toggleMenu(menuName);
+ expect(controller.navigation[menuName]).toBeTruthy();
+
+ controller.toggleMenu(menuName);
+ expect(controller.navigation).toBeNull();
+ });
+
+ it("should toggle navigation state of internal Menu 'lorem'", () => {
+ controller.toggleMenu(menuName);
+ controller.toggleMenu(internalMenuName, true);
+ expect(controller.navigation[internalMenuName]).toBeTruthy();
+
+ controller.toggleMenu(internalMenuName, true);
+ expect(controller.navigation[internalMenuName]).toBeFalsy();
+ });
+
+ it("should clear navigation", () => {
+ controller.toggleMenu(menuName);
+ controller.toggleMenu(internalMenuName, true);
+ controller.toggleMenu();
+ expect(controller.navigation).toBeNull();
+
+ // Simulate external click
+ controller.toggleMenu(menuName);
+ controller.toggleMenu(internalMenuName, true);
+ $document.triggerHandler("click");
+
+ $timeout.flush();
+ expect(controller.navigation).toBeNull();
+
+ // Simulate ESC keydown
+ controller.toggleMenu(menuName);
+ controller.toggleMenu(internalMenuName, true);
+ $document.triggerHandler({
+ type: "keydown",
+ which: controller.KEYBOARD_KEYS.ESC
+ });
+
+ $timeout.flush();
+ expect(controller.navigation).toBeNull();
+ });
+
+ it("should not clear navigation", () => {
+ controller.toggleMenu(menuName);
+ controller.toggleMenu(internalMenuName, true);
+
+ const navigation = controller.navigation;
+ component.triggerHandler("click");
+
+ $timeout.flush();
+ expect(controller.navigation).toEqual(navigation);
+ });
+
+ it("should be fixed", () => {
+ // Test without value, should be true
+ component = testUtils.compileTemplate(" ");
+ controller = component.controller("ouiNavbar");
+
+ $timeout.flush();
+
+ expect(component.hasClass(navbarFixedClass)).toBeTruthy();
+ expect(controller.fixed).toBeTruthy();
+
+ // Test with value
+ component = testUtils.compileTemplate(' ');
+ controller = component.controller("ouiNavbar");
+
+ $timeout.flush();
+
+ expect(component.hasClass(navbarFixedClass)).toBeTruthy();
+ expect(controller.fixed).toBeTruthy();
+ });
+
+ it("should not be fixed", () => {
+ component = testUtils.compileTemplate(' ');
+ controller = component.controller("ouiNavbar");
+
+ $timeout.flush();
+
+ expect(component.hasClass(navbarFixedClass)).toBeFalsy();
+ expect(controller.fixed).toBeFalsy();
+ });
+
+ it("should focus element when menu is opened", () => {
+ const name = mockData.asideLinks[0].name;
+ component = testUtils.compileTemplate(' ', {
+ asideLinks: mockData.asideLinks
+ });
+ controller = component.controller("ouiNavbar");
+
+ const button = controller.keyboardNav[name][0];
+ spyOn(button, "focus");
+ controller.toggleMenu(name);
+
+ $timeout.flush();
+ expect(button.focus).toHaveBeenCalled();
+ });
+
+ it("should have mainLinks equal togglerLinks", () => {
+ component = testUtils.compileTemplate(' ', {
+ mainLinks: mockData.mainLinks
+ });
+ controller = component.controller("ouiNavbar");
+
+ expect(controller.mainLinks).toEqual(controller.togglerLinks);
+ });
+ });
describe("Brand", () => {
const data = mockData.brand;
- let component;
let brand;
+ let component;
+ let controller;
beforeEach(() => {
- component = testUtils.compileTemplate(' ', {
+ component = testUtils.compileTemplate(`
+
+
+
+ `, {
brand: data
});
+ controller = component.find("oui-navbar-brand").controller("ouiNavbarBrand");
+
+ $timeout.flush();
+
brand = angular.element(component[0].querySelector(".oui-navbar__brand"));
});
+ it("should remove aria-label", () => {
+ expect(controller.$element.attr("aria-label")).toBeUndefined();
+ });
+
it("should create a link", () => {
expect(brand.length).toEqual(1);
expect(brand[0].tagName).toBe("A");
@@ -42,6 +268,181 @@ describe("ouiNavbar", () => {
});
});
+ describe("Dropdown", () => {
+ let component;
+
+ beforeEach(() => {
+ component = testUtils.compileTemplate(`
+
+
+
+
+
+
+
+ `, {
+ name: "foo",
+ title: "bar",
+ label: "lorem",
+ badge: 5,
+ icon: "oui-icon oui-icon-help_circle",
+ text: "Lorem ipsum"
+ });
+
+ $timeout.flush();
+ });
+
+ it("should have default classname", () => {
+ const dropdown = component.find("oui-navbar-dropdown");
+ const dropdownMenu = component.find("oui-navbar-dropdown-menu");
+
+ expect(dropdown.hasClass(navbarDropdownClass)).toBeTruthy();
+ expect(dropdown.hasClass(navbarListItemClass)).toBeTruthy();
+
+ expect(dropdownMenu.hasClass(navbarMenuClass)).toBeTruthy();
+ expect(dropdownMenu.hasClass(navbarMenuFixedClass)).toBeTruthy();
+ });
+
+ it("should have content transcluded", () => {
+ const dropdownMenu = component.find("oui-navbar-dropdown-menu");
+
+ expect(dropdownMenu.text()).toBe("Lorem ipsum");
+ });
+
+ it("should have alignment 'end'", () => {
+ component = testUtils.compileTemplate(`
+
+
+
+
+
+
+
+ `, {
+ name: "foo",
+ title: "bar",
+ label: "lorem",
+ badge: 5,
+ icon: "oui-icon oui-icon-help_circle",
+ text: "Lorem ipsum"
+ });
+ const dropdownMenu = component.find("oui-navbar-dropdown-menu");
+
+ $timeout.flush();
+
+ expect(dropdownMenu.hasClass(navbarMenuEndClass)).toBeTruthy();
+ });
+ });
+
+ describe("Menu", () => {
+ const data = mockData.asideLinks[0];
+
+ let component;
+ let menu;
+
+ beforeEach(() => {
+ component = testUtils.compileTemplate(`
+
+
+
+
+
+
+
+ `, {
+ name: data.name,
+ title: data.title,
+ headerTitle: data.headerTitle,
+ subLinks: data.subLinks
+ });
+
+ $timeout.flush();
+
+ menu = component.find("oui-navbar-menu");
+ });
+
+ it("should have default classname", () => {
+ expect(menu.hasClass(navbarMenuClass)).toBeTruthy();
+ expect(menu.hasClass(navbarMenuFixedClass)).toBeFalsy();
+ expect(menu.hasClass(navbarMenuEndClass)).toBeFalsy();
+ });
+
+ it("should have role 'menu'", () => {
+ expect(menu.attr("role")).toBe("menu");
+ });
+
+ it("should have alignment", () => {
+ component = testUtils.compileTemplate(`
+
+
+
+
+
+
+
+ `, {
+ name: data.name,
+ title: data.title,
+ headerTitle: data.headerTitle,
+ subLinks: data.subLinks
+ });
+
+ $timeout.flush();
+
+ menu = component.find("oui-navbar-menu");
+ expect(menu.hasClass(navbarMenuEndClass)).toBeTruthy();
+ });
+
+ it("should be fixed", () => {
+ component = testUtils.compileTemplate(`
+
+
+
+
+
+
+
+ `, {
+ name: data.name,
+ title: data.title,
+ headerTitle: data.headerTitle,
+ subLinks: data.subLinks
+ });
+
+ $timeout.flush();
+
+ menu = component.find("oui-navbar-menu");
+ expect(menu.hasClass(navbarMenuFixedClass)).toBeTruthy();
+ });
+ });
+
describe("Main links", () => {
const data = mockData.mainLinks;
const activeIndex = 0;
@@ -55,6 +456,8 @@ describe("ouiNavbar", () => {
mainLinks: data
});
+ $timeout.flush();
+
menu = angular.element(component[0].querySelector(".oui-navbar-list_main"));
links = menu.children("li");
});
@@ -98,6 +501,9 @@ describe("ouiNavbar", () => {
component = testUtils.compileTemplate(' ', {
asideLinks: data
});
+
+ $timeout.flush();
+
menu = angular.element(component[0].querySelector(".oui-navbar-list_aside"));
links = menu.children("li");
});
@@ -154,6 +560,8 @@ describe("ouiNavbar", () => {
togglerLinks: data
});
+ $timeout.flush();
+
toggler = angular.element(component[0].querySelector(".oui-navbar-toggler"));
responsiveMenu = angular.element(component[0].querySelector(".oui-navbar-menu_toggle"));
});
@@ -185,6 +593,8 @@ describe("ouiNavbar", () => {
mainLinks: data
});
+ $timeout.flush();
+
backdrop = angular.element(component[0].querySelector(".oui-navbar-backdrop"));
toggler = angular.element(component[0].querySelector(".oui-navbar-toggler"));
});
diff --git a/packages/oui-navbar/src/keyboard-keys.constant.js b/packages/oui-navbar/src/keyboard-keys.constant.js
index 7a757d5e..88461f3f 100644
--- a/packages/oui-navbar/src/keyboard-keys.constant.js
+++ b/packages/oui-navbar/src/keyboard-keys.constant.js
@@ -1,5 +1,5 @@
export default {
- ALT: 9,
- TAB: 16,
+ TAB: 9,
+ SHIFT: 16,
ESC: 27
};
diff --git a/packages/oui-navbar/src/link/navbar-link.component.js b/packages/oui-navbar/src/link/navbar-link.component.js
new file mode 100644
index 00000000..65f2e78f
--- /dev/null
+++ b/packages/oui-navbar/src/link/navbar-link.component.js
@@ -0,0 +1,19 @@
+import controller from "./navbar-link.controller";
+import template from "./navbar-link.html";
+
+export default {
+ require: {
+ navbarCtrl: "^^ouiNavbar"
+ },
+ bindings: {
+ name: "@",
+ text: "@",
+ href: "@?",
+ state: "@?",
+ stateParams: "",
+ label: "@?ariaLabel",
+ variant: "@?"
+ },
+ controller,
+ template
+};
diff --git a/packages/oui-navbar/src/link/navbar-link.controller.js b/packages/oui-navbar/src/link/navbar-link.controller.js
new file mode 100644
index 00000000..df5d5487
--- /dev/null
+++ b/packages/oui-navbar/src/link/navbar-link.controller.js
@@ -0,0 +1,20 @@
+export default class {
+ constructor ($element, $timeout) {
+ "ngInject";
+
+ this.$element = $element;
+ this.$timeout = $timeout;
+ }
+
+ $postLink () {
+ this.$timeout(() =>
+ this.$element
+ .addClass("oui-navbar-list__item")
+ );
+ }
+
+ // Return value of "ui-sref"
+ getFullSref () {
+ return `${this.state}(${JSON.stringify(this.stateParams)})`;
+ }
+}
diff --git a/packages/oui-navbar/src/link/navbar-link.html b/packages/oui-navbar/src/link/navbar-link.html
new file mode 100644
index 00000000..2c1e4fae
--- /dev/null
+++ b/packages/oui-navbar/src/link/navbar-link.html
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/packages/oui-navbar/src/menu/navbar-menu.component.js b/packages/oui-navbar/src/menu/navbar-menu.component.js
index d2e25b66..6cba4db1 100644
--- a/packages/oui-navbar/src/menu/navbar-menu.component.js
+++ b/packages/oui-navbar/src/menu/navbar-menu.component.js
@@ -2,17 +2,18 @@ import controller from "./navbar-menu.controller";
import template from "./navbar-menu.html";
export default {
+ require: {
+ navbarCtrl: "^^ouiNavbar"
+ },
bindings: {
backButton: "",
- childrenClass: "@?",
headerBreadcrumb: "@?",
headerClass: "@?",
headerTitle: "@?",
menuLinks: "
-
@@ -76,16 +72,15 @@
@@ -94,15 +89,13 @@
+ align="{{::$ctrl.align}}"
+ fixed="::$ctrl.fixed"
+ back-button>
diff --git a/packages/oui-navbar/src/navbar.component.js b/packages/oui-navbar/src/navbar.component.js
index b3dd175c..9db97d51 100644
--- a/packages/oui-navbar/src/navbar.component.js
+++ b/packages/oui-navbar/src/navbar.component.js
@@ -12,5 +12,11 @@ export default {
fixed: ""
},
controller,
- template
+ template,
+ transclude: {
+ asideSlot: "?ouiNavbarAside",
+ brandSlot: "?ouiNavbarBrand",
+ mainSlot: "?ouiNavbarMain",
+ togglerSlot: "?ouiNavbarToggler"
+ }
};
diff --git a/packages/oui-navbar/src/navbar.controller.js b/packages/oui-navbar/src/navbar.controller.js
index 83b29588..ef0c3658 100644
--- a/packages/oui-navbar/src/navbar.controller.js
+++ b/packages/oui-navbar/src/navbar.controller.js
@@ -1,7 +1,8 @@
import { addBooleanParameter } from "@oui-angular/common/component-utils";
+import get from "lodash/get";
export default class {
- constructor ($attrs, $document, $element, $timeout, ouiNavbarConfiguration, NavbarService, KEYBOARD_KEYS) {
+ constructor ($attrs, $document, $element, $timeout, ouiNavbarConfiguration, KEYBOARD_KEYS) {
"ngInject";
this.$attrs = $attrs;
@@ -9,37 +10,76 @@ export default class {
this.$element = $element;
this.$timeout = $timeout;
this.config = ouiNavbarConfiguration;
- this.navbarService = NavbarService;
+
+ this.keyboardNav = {};
this.KEYBOARD_KEYS = KEYBOARD_KEYS;
}
+ addItemToGroup (item, groupName) {
+ // Create the group if it doesn't exist
+ if (angular.isUndefined(this.keyboardNav[groupName])) {
+ this.keyboardNav[groupName] = [];
+ }
+
+ // Add item to the group
+ this.keyboardNav[groupName].push(item);
+ }
+
+ getGroup (groupName) {
+ return this.keyboardNav[groupName];
+ }
+
+ // Set focus to an item of a group
+ setFocusTo (groupName, index) {
+ // Add a delay to force focus
+ const delay = 50;
+ this.$timeout(() => {
+ if (get(this.keyboardNav, `${groupName}[${index}]`)) {
+ this.keyboardNav[groupName][index].focus();
+ }
+ }, delay);
+ }
+
toggleMenu (state, isInternalNav) {
- // Update navbar navigation
- this.navigation = this.navbarService.toggleMenu(state, isInternalNav);
+ if (state) {
+ // Reset navigation if not internal navigation
+ if (!isInternalNav && (!this.navigation || !this.navigation[state])) {
+ this.navigation = {};
+ }
+
+ if (isInternalNav || !this.navigation[state]) {
+ // Toggle menu if internal navigation or state is undefined
+ this.navigation[state] = !this.navigation[state];
+ } else if (this.navigation[state]) {
+ // Else close all menus
+ this.navigation = null;
+ }
+
+ // Focus first list item when opened
+ if (this.navigation && this.navigation[state]) {
+ // Add a little delay to avoid transition bug on Webkit
+ this.setFocusTo(state, 0);
+ }
+ } else {
+ // Close all menus
+ this.navigation = null;
+ }
}
- updateLinks () {
+ $onInit () {
// If no togglerLinks attribute, we use the value of mainLinks
if (!angular.isDefined(this.$attrs.togglerLinks) && angular.isDefined(this.$attrs.mainLinks)) {
this.togglerLinks = this.mainLinks;
- this.togglerLoaded = true;
}
- }
-
- $onInit () {
- this.updateLinks();
// Support presence of attribute 'fixed'
addBooleanParameter(this, "fixed");
}
- $onChanges (changes) {
- this.updateLinks();
-
- // Get togglerLinks changes for the loader
- if (changes.togglerLinks) {
- this.togglerLoaded = !!changes.togglerLinks.currentValue;
- }
+ $onDestroy () {
+ this.$document
+ .off("click")
+ .off("keydown");
}
$postLink () {
@@ -47,6 +87,7 @@ export default class {
this.$timeout(() => {
// Add Classname on root element
this.$element.addClass("oui-navbar");
+
if (this.fixed) {
this.$element.addClass("oui-navbar_fixed");
}
diff --git a/packages/oui-navbar/src/navbar.html b/packages/oui-navbar/src/navbar.html
index ce09a1e7..12cd08a9 100644
--- a/packages/oui-navbar/src/navbar.html
+++ b/packages/oui-navbar/src/navbar.html
@@ -1,228 +1,108 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
-
+
+
-