Skip to content

Commit

Permalink
feat(components): new Orange navbar component (#1099)
Browse files Browse the repository at this point in the history
Co-authored-by: Kevin Le Diouron <kevin.lediouron.ext@orange.com>
Co-authored-by: Julien Déramond <julien.deramond@orange.com>
Co-authored-by: Isabelle Chanclou <isabelle.chanclou@orange.com>
Co-authored-by: Louis-Maxime Piton <louismaxime.piton@orange.com>
Co-authored-by: Mewen Le Hô <mewen.leho.ext@orange.com>
  • Loading branch information
5 people authored Apr 5, 2022
1 parent b467da2 commit 46b2202
Show file tree
Hide file tree
Showing 31 changed files with 1,555 additions and 359 deletions.
4 changes: 2 additions & 2 deletions .bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
},
{
"path": "./dist/css/boosted.css",
"maxSize": "31.6 kB"
"maxSize": "32.5 kB"
},
{
"path": "./dist/css/boosted.min.css",
"maxSize": "29.4 kB"
"maxSize": "30.1 kB"
},
{
"path": "./dist/js/boosted.bundle.js",
Expand Down
3 changes: 2 additions & 1 deletion build/vnu-jar.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ execFile('java', ['-version'], (error, stdout, stderr) => {
// and offer more robust alternatives, but also need to show a less-than-ideal example
'An “aria-disabled” attribute whose value is “true” should not be specified on an “a” element that has an “href” attribute.',
// Boosted mod: `role="img"` is needed for `<img "src=.svg" alt="">`
'The “img” role is unnecessary for element “img”.'
'The “img” role is unnecessary for element “img”.',
'.*Consider using the “h1” element as a top-level heading only.*'
].join('|')

const args = [
Expand Down
1 change: 1 addition & 0 deletions js/index.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { default as Collapse } from './src/collapse'
export { default as Dropdown } from './src/dropdown'
export { default as Modal } from './src/modal'
export { default as Offcanvas } from './src/offcanvas'
export { default as OrangeNavbar } from './src/orange-navbar'
export { default as Popover } from './src/popover'
export { default as QuantitySelector } from './src/quantity-selector' // Boosted mod
export { default as ScrollSpy } from './src/scrollspy'
Expand Down
2 changes: 2 additions & 0 deletions js/index.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Collapse from './src/collapse'
import Dropdown from './src/dropdown'
import Modal from './src/modal'
import Offcanvas from './src/offcanvas'
import OrangeNavbar from './src/orange-navbar'
import Popover from './src/popover'
import QuantitySelector from './src/quantity-selector' // Boosted mod
import ScrollSpy from './src/scrollspy'
Expand All @@ -28,6 +29,7 @@ export default {
Dropdown,
Modal,
Offcanvas,
OrangeNavbar,
Popover,
QuantitySelector, // Boosted mod
ScrollSpy,
Expand Down
19 changes: 11 additions & 8 deletions js/src/collapse.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,20 +184,23 @@ class Collapse extends BaseComponent {
this._element.classList.add(CLASS_NAME_COLLAPSING)
this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)

for (const trigger of this._triggerArray) {
const element = getElementFromSelector(trigger)

if (element && !this._isShown(element)) {
this._addAriaAndCollapsedClass([trigger], false)
}
}

this._isTransitioning = true

const complete = () => {
this._isTransitioning = false
this._element.classList.remove(CLASS_NAME_COLLAPSING)
this._element.classList.add(CLASS_NAME_COLLAPSE)

// Boosted mod: Change the moment of the appliance of .collapsed
for (const trigger of this._triggerArray) {
const element = getElementFromSelector(trigger)

if (element && !this._isShown(element)) {
this._addAriaAndCollapsedClass([trigger], false)
}
}
// End mod

EventHandler.trigger(this._element, EVENT_HIDDEN)
}

Expand Down
91 changes: 91 additions & 0 deletions js/src/orange-navbar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* --------------------------------------------------------------------------
* Boosted (v5.1.3): orange-navbar.js
* Licensed under MIT (https://github.com/Orange-OpenSource/Orange-Boosted-Bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/

import { defineJQueryPlugin } from './util/index'
import EventHandler from './dom/event-handler'
import BaseComponent from './base-component'
import SelectorEngine from './dom/selector-engine'

/**
* Constants
*/

const NAME = 'orangenavbar'
const DATA_KEY = 'bs.orangenavbar'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const EVENT_SCROLL_DATA_API = `scroll${EVENT_KEY}${DATA_API_KEY}`
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
const SELECTOR_STICKY_TOP = 'header.sticky-top'

/**
* Class definition
*/

class OrangeNavbar extends BaseComponent {
// Getters
static get NAME() {
return NAME
}

// Static
static enableMinimizing(el) {
// The minimized behaviour works only if your header has .sticky-top (fixed-top will be sticky without minimizing)
const scroll = window.scrollY
const headerChildren = [...el.children]
const globalHeaderChild = headerChildren.find(element => !element.classList.contains('supra'))

if (globalHeaderChild) {
if (scroll > 0) {
// Consider first element not having .supra in array is the first header
globalHeaderChild.classList.add('header-minimized')
} else {
globalHeaderChild.classList.remove('header-minimized')
}
}
}

static jQueryInterface(config) {
return this.each(function () {
const data = OrangeNavbar.getOrCreateInstance(this, config)

if (typeof config !== 'string') {
return
}

if (typeof data[config] === 'undefined') {
throw new TypeError(`No method named "${config}"`)
}

data[config]()
})
}
}

/**
* Data API implementation
*/

EventHandler.on(window, EVENT_SCROLL_DATA_API, () => {
for (const el of SelectorEngine.find(SELECTOR_STICKY_TOP)) {
OrangeNavbar.enableMinimizing(el)
}
})

EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
for (const el of SelectorEngine.find(SELECTOR_STICKY_TOP)) {
OrangeNavbar.enableMinimizing(el)
}
})

/**
* jQuery
*/

defineJQueryPlugin(OrangeNavbar)

export default OrangeNavbar
185 changes: 185 additions & 0 deletions js/tests/unit/orange-navbar.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import OrangeNavbar from '../../src/orange-navbar'
import { clearFixture, getFixture, createEvent, jQueryMock } from '../helpers/fixture'

describe('OrangeNavbar', () => {
let fixtureEl

beforeAll(() => {
fixtureEl = getFixture()
})

afterEach(() => {
clearFixture()
})

describe('VERSION', () => {
it('should return plugin version', () => {
expect(OrangeNavbar.VERSION).toEqual(jasmine.any(String))
})
})

describe('Default', () => {
it('should return plugin default config', () => {
expect(OrangeNavbar.Default).toEqual(jasmine.any(Object))
})
})

describe('DefaultType', () => {
it('should return plugin default type config', () => {
expect(OrangeNavbar.DefaultType).toEqual(jasmine.any(Object))
})
})

describe('DATA_KEY', () => {
it('should return plugin data key', () => {
expect(OrangeNavbar.DATA_KEY).toEqual('bs.orangenavbar')
})
})

describe('jQueryInterface', () => {
it('should create an orange navbar', () => {
fixtureEl.innerHTML = '<div></div>'

const div = fixtureEl.querySelector('div')

jQueryMock.fn.orangenavbar = OrangeNavbar.jQueryInterface
jQueryMock.elements = [div]

jQueryMock.fn.orangenavbar.call(jQueryMock)

expect(OrangeNavbar.getInstance(div)).not.toBeNull()
})

it('should not re create an orange navbar', () => {
fixtureEl.innerHTML = '<div></div>'

const div = fixtureEl.querySelector('div')
const orangenavbar = new OrangeNavbar(div)

jQueryMock.fn.orangenavbar = OrangeNavbar.jQueryInterface
jQueryMock.elements = [div]

jQueryMock.fn.orangenavbar.call(jQueryMock)

expect(OrangeNavbar.getInstance(div)).toEqual(orangenavbar)
})

it('should throw error on undefined method', () => {
fixtureEl.innerHTML = '<div></div>'

const div = fixtureEl.querySelector('div')
const action = 'undefinedMethod'

jQueryMock.fn.orangenavbar = OrangeNavbar.jQueryInterface
jQueryMock.elements = [div]

expect(() => {
jQueryMock.fn.orangenavbar.call(jQueryMock, action)
}).toThrowError(TypeError, `No method named "${action}"`)
})
})

it('should call enableMinimizing when the page finish loading', () => {
fixtureEl.innerHTML = [
'<header class="sticky-top"></header>'
].join('')

const loadEvent = createEvent('load')
spyOn(OrangeNavbar, 'enableMinimizing')

window.dispatchEvent(loadEvent)

expect(OrangeNavbar.enableMinimizing).toHaveBeenCalled()
})

it('should call enableMinimizing when there is a scroll on the page', () => {
fixtureEl.innerHTML = [
'<header class="sticky-top" style="height: 20000px;"></header>'
].join('')

const scrollEvent = createEvent('scroll')
spyOn(OrangeNavbar, 'enableMinimizing')
window.scrollY = 1

window.dispatchEvent(scrollEvent)

expect(OrangeNavbar.enableMinimizing).toHaveBeenCalled()
})

it('should not call enableMinimizing when there is a scroll on the page and the header does not have .sticky-top', () => {
fixtureEl.innerHTML = [
'<header style="height: 20000px;"></header>'
].join('')

const scrollEvent = createEvent('scroll')
spyOn(OrangeNavbar, 'enableMinimizing')
window.scrollY = 1

window.dispatchEvent(scrollEvent)

expect(OrangeNavbar.enableMinimizing).not.toHaveBeenCalled()
})

it('should add .header-minimized to the global header non-supra first <nav> when enableMinimizing is called not at the top of the page', () => {
fixtureEl.innerHTML = [
'<header class="sticky-top" style="height: 20000px;">',
' <nav id="notTargeted" class="supra"></nav>',
' <nav id="target"></nav>',
' <nav id="notTargeted2"></nav>',
'</header>'
].join('')

const targetEl = fixtureEl.querySelector('#target')
const notTargetedEl = fixtureEl.querySelector('#notTargeted')
const notTargeted2El = fixtureEl.querySelector('#notTargeted2')
window.scrollY = 1

OrangeNavbar.enableMinimizing(fixtureEl.querySelector('header.sticky-top'))

expect(targetEl).toHaveClass('header-minimized')
expect(notTargetedEl).not.toHaveClass('header-minimized')
expect(notTargeted2El).not.toHaveClass('header-minimized')
})

it('should remove .header-minimized to the global header non-supra first <nav> when enableMinimizing is called at the top of the page', () => {
fixtureEl.innerHTML = [
'<header class="sticky-top" style="height: 20000px;">',
' <nav id="notTargeted" class="supra"></nav>',
' <nav id="target" class="header-minimized"></nav>',
' <nav id="notTargeted2"></nav>',
'</header>'
].join('')

const targetEl = fixtureEl.querySelector('#target')
const notTargetedEl = fixtureEl.querySelector('#notTargeted')
const notTargeted2El = fixtureEl.querySelector('#notTargeted2')
window.scrollY = 0

OrangeNavbar.enableMinimizing(fixtureEl.querySelector('header.sticky-top'))

expect(targetEl).not.toHaveClass('header-minimized')
expect(notTargetedEl).not.toHaveClass('header-minimized')
expect(notTargeted2El).not.toHaveClass('header-minimized')
})

it('should not add .header-minimized to the global header non-supra first <nav> when the header only has .supra <nav>', () => {
fixtureEl.innerHTML = [
'<header class="sticky-top" style="height: 20000px;">',
' <nav id="target0" class="supra"></nav>',
' <nav id="target1" class="supra"></nav>',
' <nav id="target2" class="supra"></nav>',
'</header>'
].join('')

const target0El = fixtureEl.querySelector('#target0')
const target1El = fixtureEl.querySelector('#target1')
const target2El = fixtureEl.querySelector('#target2')
window.scrollY = 1

OrangeNavbar.enableMinimizing(fixtureEl.querySelector('header.sticky-top'))

expect(target0El).not.toHaveClass('header-minimized')
expect(target1El).not.toHaveClass('header-minimized')
expect(target2El).not.toHaveClass('header-minimized')
})
})
Loading

0 comments on commit 46b2202

Please sign in to comment.