import { tagName } from '@ember-decorators/component';
import { observes } from '@ember-decorators/object';
import { action, computed } from '@ember/object';
import Component from '@ember/component';
import listenTo from 'ember-bootstrap/utils/cp/listen-to';
import defaultValue from 'ember-bootstrap/utils/default-decorator';
import { assert } from '@ember/debug';
import { isBlank } from '@ember/utils';
import deprecateSubclassing from 'ember-bootstrap/utils/deprecate-subclassing';
/**
Component to generate [Bootstrap navbars](http://getbootstrap.com/components/#navbar).
### Usage
Uses the following components by a contextual reference:
```hbs
<BsNavbar as |navbar|>
<div class="navbar-header">
<navbar.toggle />
<a class="navbar-brand" href="#">Brand</a>
</div>
<navbar.content>
<navbar.nav as |nav|>
<nav.item>
<nav.linkTo @route="home">Home</nav.linkTo>
</nav.item>
<nav.item>
<nav.linkTo @route="navbars">Navbars</nav.linkTo>
</nav.item>
</navbar.nav>
</navbar.content>
</BsNavbar>
```
**Note:** the `<div class="navbar-header">` is required for BS3 to hold the elements visible on a mobile breakpoint,
when the actual content is collapsed. It should *not* be used for BS4!
The component yields references to the following contextual components:
* [Components.NavbarContent](Components.NavbarContent.html)
* [Components.NavbarToggle](Components.NavbarToggle.html)
* [Components.NavbarNav](Components.NavbarNav.html)
Furthermore references to the following actions are yielded:
* `collapse`: triggers the `onCollapse` action and collapses the navbar (mobile)
* `expand`: triggers the `onExpand` action and expands the navbar (mobile)
* `toggleNavbar`: triggers the `toggleNavbar` action and toggles the navbar (mobile)
### Responsive Design
For the mobile breakpoint the Bootstrap styles will hide the navbar content (`{{navbar.content}}`). Clicking on the
navbar toggle button (`{{navbar.toggle}}`) will expand the menu. By default all nav links (`<nav.linkTo @route="...">`) are already
wired up to call the navbar's `collapse` action, so clicking any of them will collapse the navbar. To selectively
prevent that, you can set its `collapseNavbar` property to false:
```hbs
<nav.item>
<nav.linkTo @route="index" @collapseNavbar={{false}}>Don't collapse</nav.linkTo>
</nav.item>
```
To collapse the navbar when clicking on some nav items that are not internal links, you can use the yielded `collapse`
action:
```hbs
<BsNavbar as |navbar|>
<navbar.content>
<navbar.nav as |nav|>
<nav.item>
<a onclick={{action navbar.collapse}}>Collapse</a>
</nav.item>
</navbar.nav>
</navbar.content>
</BsNavbar>
```
### Navbar styles
The component supports the default bootstrap navbar styling options through the `type`
property. Bootstrap navbars [do not currently support justified nav links](http://getbootstrap.com/components/#navbar-default),
so those are explicitly disallowed.
Other bootstrap navbar variations, such as forms, buttons, etc. can be supported through direct use of
bootstrap styles applied through the `class` attribute on the components.
### Bootstrap 3/4 Notes
Bootstrap 4 changed the default navbar styling option from `navbar-default` to `navbar-light`.
If you explicitly specified "default" in Bootstrap 3 and are migrating, you will need to change
this in your code. Bootstrap 4 changes `navbar-inverse` to `navbar-dark`.
Bootstrap 4 navbars are fluid by default without the need for an additional container. An
additional container is added like with Bootstrap 3 if `fluid` is `false`.
*Note that only invoking the component in a template as shown above is considered part of its public API. Extending from it (subclassing) is generally not supported, and may break at any time.*
@class Navbar
@namespace Components
@extends Ember.Component
@public
*/
@tagName('')
@deprecateSubclassing
export default class Navbar extends Component {
/**
* Manages the state for the responsive menu between the toggle and the content.
*
* @property collapsed
* @type boolean
* @default true
* @public
*/
@defaultValue
collapsed = true;
/**
* @property _collapsed
* @private
*/
@listenTo('collapsed')
_collapsed;
/**
* Controls whether the wrapping div is a fluid container or not.
*
* @property fluid
* @type boolean
* @default true
* @public
*/
@defaultValue
fluid = true;
/**
* BS5 only: Allows to override the container layout, see https://getbootstrap.com/docs/5.0/components/navbar/#containers.
* Allowed values: `'sm'`, `'md'`, `'lg'`, `'xl'`, `'xxl'`, `'fluid'`, see https://getbootstrap.com/docs/5.0/layout/containers/.
* By default it is `.container-fluid`, or `.container` if the `@fluid` argument is set to false.
*
* @property container
* @type string
* @public
*/
@computed('fluid', 'container')
get containerClass() {
if (this.container) {
return `container-${this.container}`;
}
return this.fluid ? 'container-fluid' : 'container';
}
/**
* Specifies the position classes for the navbar, currently supporting none, "fixed-top", "fixed-bottom", and
* either "static-top" (BS3) or "sticky-top" (BS4).
* See the [bootstrap docs](http://getbootstrap.com/components/#navbar-fixed-top) for details.
*
* @property position
* @type String
* @default null
* @public
*/
@defaultValue
position = null;
@computed('position')
get positionClass() {
let position = this.position;
let validPositions = ['fixed-top', 'fixed-bottom', 'sticky-top'];
if (validPositions.indexOf(position) === -1) {
return null;
}
return position;
}
/**
* Property for type styling
*
* For the available types see the [Bootstrap docs](https://getbootstrap.com/docs/4.3/components/navbar/#color-schemes)
*
* @property type
* @type String
* @default 'light'
* @public
*/
@defaultValue
type = 'light';
@computed('type')
get typeClass() {
let type = this.type || 'light';
assert('The value of `type` must be a string', typeof type === 'string' && type !== '');
return `navbar-${type}`;
}
/**
* The action to be sent when the navbar is about to be collapsed.
*
* You can return false to prevent collapsing the navbar automatically, and do that in your action by
* setting `collapsed` to true.
*
* @event onCollapse
* @public
*/
onCollapse() {}
/**
* The action to be sent after the navbar has been collapsed (including the CSS transition).
*
* @event onCollapsed
* @public
*/
onCollapsed() {}
/**
* The action to be sent when the navbar is about to be expanded.
*
* You can return false to prevent expanding the navbar automatically, and do that in your action by
* setting `collapsed` to false.
*
* @event onExpand
* @public
*/
onExpand() {}
/**
* The action to be sent after the navbar has been expanded (including the CSS transition).
*
* @event onExpanded
* @public
*/
onExpanded() {}
@observes('_collapsed')
_onCollapsedChange() {
let collapsed = this._collapsed;
let active = this.active;
if (collapsed !== active) {
return;
}
if (collapsed === false) {
this.show();
} else {
this.hide();
}
}
/**
* @method expand
* @private
*/
@action
expand() {
if (this.onExpand() !== false) {
this.set('_collapsed', false);
}
}
/**
* @method collapse
* @private
*/
@action
collapse() {
if (this.onCollapse() !== false) {
this.set('_collapsed', true);
}
}
@action
toggleNavbar() {
if (this._collapsed) {
this.expand();
} else {
this.collapse();
}
}
/**
* Bootstrap 4 Only: Defines the responsive toggle breakpoint size. Options are the standard
* two character Bootstrap size abbreviations. Used to set the `navbar-expand-*`
* class. Set to `null` to disable collapsing.
*
* @property toggleBreakpoint
* @type String
* @default 'lg'
* @public
*/
@defaultValue
toggleBreakpoint = 'lg';
/**
* Bootstrap 4 Only: Sets the background color for the navbar. Can be any color
* in the set that composes the `bg-*` classes and can be extended by creating your
* own `bg-*` classes.
*
* @property backgroundColor
* @type String
* @default 'light'
* @public
*/
@defaultValue
backgroundColor = 'light';
@computed('toggleBreakpoint')
get breakpointClass() {
let toggleBreakpoint = this.toggleBreakpoint;
if (isBlank(toggleBreakpoint)) {
return 'navbar-expand';
} else {
return `navbar-expand-${toggleBreakpoint}`;
}
}
@computed('backgroundColor')
get backgroundClass() {
return `bg-${this.backgroundColor}`;
}
/**
* @property toggleComponent
* @type {String}
* @private
*/
/**
* @property contentComponent
* @type {String}
* @private
*/
/**
* @property navComponent
* @type {String}
* @private
*/
/**
* @property linkToComponent
* @type {String}
* @private
*/
}