| @@ -0,0 +1,176 @@ | ||
| /** | ||
| * -------------------------------------------------------------------------- | ||
| * Bootstrap (v4.0.0-alpha.6): button.js | ||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||
| * -------------------------------------------------------------------------- | ||
| */ | ||
|
|
||
| const Button = (($) => { | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Constants | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| const NAME = 'button' | ||
| const VERSION = '4.0.0-alpha.6' | ||
| const DATA_KEY = 'bs.button' | ||
| const EVENT_KEY = `.${DATA_KEY}` | ||
| const DATA_API_KEY = '.data-api' | ||
| const JQUERY_NO_CONFLICT = $.fn[NAME] | ||
|
|
||
| const ClassName = { | ||
| ACTIVE : 'active', | ||
| BUTTON : 'btn', | ||
| FOCUS : 'focus' | ||
| } | ||
|
|
||
| const Selector = { | ||
| DATA_TOGGLE_CARROT : '[data-toggle^="button"]', | ||
| DATA_TOGGLE : '[data-toggle="buttons"]', | ||
| INPUT : 'input', | ||
| ACTIVE : '.active', | ||
| BUTTON : '.btn' | ||
| } | ||
|
|
||
| const Event = { | ||
| CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`, | ||
| FOCUS_BLUR_DATA_API : `focus${EVENT_KEY}${DATA_API_KEY} ` | ||
| + `blur${EVENT_KEY}${DATA_API_KEY}` | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Class Definition | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| class Button { | ||
|
|
||
| constructor(element) { | ||
| this._element = element | ||
| } | ||
|
|
||
|
|
||
| // getters | ||
|
|
||
| static get VERSION() { | ||
| return VERSION | ||
| } | ||
|
|
||
|
|
||
| // public | ||
|
|
||
| toggle() { | ||
| let triggerChangeEvent = true | ||
| const rootElement = $(this._element).closest( | ||
| Selector.DATA_TOGGLE | ||
| )[0] | ||
|
|
||
| if (rootElement) { | ||
| const input = $(this._element).find(Selector.INPUT)[0] | ||
|
|
||
| if (input) { | ||
| if (input.type === 'radio') { | ||
| if (input.checked && | ||
| $(this._element).hasClass(ClassName.ACTIVE)) { | ||
| triggerChangeEvent = false | ||
|
|
||
| } else { | ||
| const activeElement = $(rootElement).find(Selector.ACTIVE)[0] | ||
|
|
||
| if (activeElement) { | ||
| $(activeElement).removeClass(ClassName.ACTIVE) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (triggerChangeEvent) { | ||
| input.checked = !$(this._element).hasClass(ClassName.ACTIVE) | ||
| $(input).trigger('change') | ||
| } | ||
|
|
||
| input.focus() | ||
| } | ||
|
|
||
| } | ||
|
|
||
| this._element.setAttribute('aria-pressed', | ||
| !$(this._element).hasClass(ClassName.ACTIVE)) | ||
|
|
||
| if (triggerChangeEvent) { | ||
| $(this._element).toggleClass(ClassName.ACTIVE) | ||
| } | ||
| } | ||
|
|
||
| dispose() { | ||
| $.removeData(this._element, DATA_KEY) | ||
| this._element = null | ||
| } | ||
|
|
||
|
|
||
| // static | ||
|
|
||
| static _jQueryInterface(config) { | ||
| return this.each(function () { | ||
| let data = $(this).data(DATA_KEY) | ||
|
|
||
| if (!data) { | ||
| data = new Button(this) | ||
| $(this).data(DATA_KEY, data) | ||
| } | ||
|
|
||
| if (config === 'toggle') { | ||
| data[config]() | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Data Api implementation | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $(document) | ||
| .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE_CARROT, (event) => { | ||
| event.preventDefault() | ||
|
|
||
| let button = event.target | ||
|
|
||
| if (!$(button).hasClass(ClassName.BUTTON)) { | ||
| button = $(button).closest(Selector.BUTTON) | ||
| } | ||
|
|
||
| Button._jQueryInterface.call($(button), 'toggle') | ||
| }) | ||
| .on(Event.FOCUS_BLUR_DATA_API, Selector.DATA_TOGGLE_CARROT, (event) => { | ||
| const button = $(event.target).closest(Selector.BUTTON)[0] | ||
| $(button).toggleClass(ClassName.FOCUS, /^focus(in)?$/.test(event.type)) | ||
| }) | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * jQuery | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $.fn[NAME] = Button._jQueryInterface | ||
| $.fn[NAME].Constructor = Button | ||
| $.fn[NAME].noConflict = function () { | ||
| $.fn[NAME] = JQUERY_NO_CONFLICT | ||
| return Button._jQueryInterface | ||
| } | ||
|
|
||
| return Button | ||
|
|
||
| })(jQuery) | ||
|
|
||
| export default Button |
| @@ -0,0 +1,387 @@ | ||
| import Util from './util' | ||
|
|
||
|
|
||
| /** | ||
| * -------------------------------------------------------------------------- | ||
| * Bootstrap (v4.0.0-alpha.6): collapse.js | ||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||
| * -------------------------------------------------------------------------- | ||
| */ | ||
|
|
||
| const Collapse = (($) => { | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Constants | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| const NAME = 'collapse' | ||
| const VERSION = '4.0.0-alpha.6' | ||
| const DATA_KEY = 'bs.collapse' | ||
| const EVENT_KEY = `.${DATA_KEY}` | ||
| const DATA_API_KEY = '.data-api' | ||
| const JQUERY_NO_CONFLICT = $.fn[NAME] | ||
| const TRANSITION_DURATION = 600 | ||
|
|
||
| const Default = { | ||
| toggle : true, | ||
| parent : '' | ||
| } | ||
|
|
||
| const DefaultType = { | ||
| toggle : 'boolean', | ||
| parent : 'string' | ||
| } | ||
|
|
||
| const Event = { | ||
| SHOW : `show${EVENT_KEY}`, | ||
| SHOWN : `shown${EVENT_KEY}`, | ||
| HIDE : `hide${EVENT_KEY}`, | ||
| HIDDEN : `hidden${EVENT_KEY}`, | ||
| CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` | ||
| } | ||
|
|
||
| const ClassName = { | ||
| SHOW : 'show', | ||
| COLLAPSE : 'collapse', | ||
| COLLAPSING : 'collapsing', | ||
| COLLAPSED : 'collapsed' | ||
| } | ||
|
|
||
| const Dimension = { | ||
| WIDTH : 'width', | ||
| HEIGHT : 'height' | ||
| } | ||
|
|
||
| const Selector = { | ||
| ACTIVES : '.card > .show, .card > .collapsing', | ||
| DATA_TOGGLE : '[data-toggle="collapse"]' | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Class Definition | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| class Collapse { | ||
|
|
||
| constructor(element, config) { | ||
| this._isTransitioning = false | ||
| this._element = element | ||
| this._config = this._getConfig(config) | ||
| this._triggerArray = $.makeArray($( | ||
| `[data-toggle="collapse"][href="#${element.id}"],` + | ||
| `[data-toggle="collapse"][data-target="#${element.id}"]` | ||
| )) | ||
|
|
||
| this._parent = this._config.parent ? this._getParent() : null | ||
|
|
||
| if (!this._config.parent) { | ||
| this._addAriaAndCollapsedClass(this._element, this._triggerArray) | ||
| } | ||
|
|
||
| if (this._config.toggle) { | ||
| this.toggle() | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // getters | ||
|
|
||
| static get VERSION() { | ||
| return VERSION | ||
| } | ||
|
|
||
| static get Default() { | ||
| return Default | ||
| } | ||
|
|
||
|
|
||
| // public | ||
|
|
||
| toggle() { | ||
| if ($(this._element).hasClass(ClassName.SHOW)) { | ||
| this.hide() | ||
| } else { | ||
| this.show() | ||
| } | ||
| } | ||
|
|
||
| show() { | ||
| if (this._isTransitioning) { | ||
| throw new Error('Collapse is transitioning') | ||
| } | ||
|
|
||
| if ($(this._element).hasClass(ClassName.SHOW)) { | ||
| return | ||
| } | ||
|
|
||
| let actives | ||
| let activesData | ||
|
|
||
| if (this._parent) { | ||
| actives = $.makeArray($(this._parent).find(Selector.ACTIVES)) | ||
| if (!actives.length) { | ||
| actives = null | ||
| } | ||
| } | ||
|
|
||
| if (actives) { | ||
| activesData = $(actives).data(DATA_KEY) | ||
| if (activesData && activesData._isTransitioning) { | ||
| return | ||
| } | ||
| } | ||
|
|
||
| const startEvent = $.Event(Event.SHOW) | ||
| $(this._element).trigger(startEvent) | ||
| if (startEvent.isDefaultPrevented()) { | ||
| return | ||
| } | ||
|
|
||
| if (actives) { | ||
| Collapse._jQueryInterface.call($(actives), 'hide') | ||
| if (!activesData) { | ||
| $(actives).data(DATA_KEY, null) | ||
| } | ||
| } | ||
|
|
||
| const dimension = this._getDimension() | ||
|
|
||
| $(this._element) | ||
| .removeClass(ClassName.COLLAPSE) | ||
| .addClass(ClassName.COLLAPSING) | ||
|
|
||
| this._element.style[dimension] = 0 | ||
| this._element.setAttribute('aria-expanded', true) | ||
|
|
||
| if (this._triggerArray.length) { | ||
| $(this._triggerArray) | ||
| .removeClass(ClassName.COLLAPSED) | ||
| .attr('aria-expanded', true) | ||
| } | ||
|
|
||
| this.setTransitioning(true) | ||
|
|
||
| const complete = () => { | ||
| $(this._element) | ||
| .removeClass(ClassName.COLLAPSING) | ||
| .addClass(ClassName.COLLAPSE) | ||
| .addClass(ClassName.SHOW) | ||
|
|
||
| this._element.style[dimension] = '' | ||
|
|
||
| this.setTransitioning(false) | ||
|
|
||
| $(this._element).trigger(Event.SHOWN) | ||
| } | ||
|
|
||
| if (!Util.supportsTransitionEnd()) { | ||
| complete() | ||
| return | ||
| } | ||
|
|
||
| const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1) | ||
| const scrollSize = `scroll${capitalizedDimension}` | ||
|
|
||
| $(this._element) | ||
| .one(Util.TRANSITION_END, complete) | ||
| .emulateTransitionEnd(TRANSITION_DURATION) | ||
|
|
||
| this._element.style[dimension] = `${this._element[scrollSize]}px` | ||
| } | ||
|
|
||
| hide() { | ||
| if (this._isTransitioning) { | ||
| throw new Error('Collapse is transitioning') | ||
| } | ||
|
|
||
| if (!$(this._element).hasClass(ClassName.SHOW)) { | ||
| return | ||
| } | ||
|
|
||
| const startEvent = $.Event(Event.HIDE) | ||
| $(this._element).trigger(startEvent) | ||
| if (startEvent.isDefaultPrevented()) { | ||
| return | ||
| } | ||
|
|
||
| const dimension = this._getDimension() | ||
| const offsetDimension = dimension === Dimension.WIDTH ? | ||
| 'offsetWidth' : 'offsetHeight' | ||
|
|
||
| this._element.style[dimension] = `${this._element[offsetDimension]}px` | ||
|
|
||
| Util.reflow(this._element) | ||
|
|
||
| $(this._element) | ||
| .addClass(ClassName.COLLAPSING) | ||
| .removeClass(ClassName.COLLAPSE) | ||
| .removeClass(ClassName.SHOW) | ||
|
|
||
| this._element.setAttribute('aria-expanded', false) | ||
|
|
||
| if (this._triggerArray.length) { | ||
| $(this._triggerArray) | ||
| .addClass(ClassName.COLLAPSED) | ||
| .attr('aria-expanded', false) | ||
| } | ||
|
|
||
| this.setTransitioning(true) | ||
|
|
||
| const complete = () => { | ||
| this.setTransitioning(false) | ||
| $(this._element) | ||
| .removeClass(ClassName.COLLAPSING) | ||
| .addClass(ClassName.COLLAPSE) | ||
| .trigger(Event.HIDDEN) | ||
| } | ||
|
|
||
| this._element.style[dimension] = '' | ||
|
|
||
| if (!Util.supportsTransitionEnd()) { | ||
| complete() | ||
| return | ||
| } | ||
|
|
||
| $(this._element) | ||
| .one(Util.TRANSITION_END, complete) | ||
| .emulateTransitionEnd(TRANSITION_DURATION) | ||
| } | ||
|
|
||
| setTransitioning(isTransitioning) { | ||
| this._isTransitioning = isTransitioning | ||
| } | ||
|
|
||
| dispose() { | ||
| $.removeData(this._element, DATA_KEY) | ||
|
|
||
| this._config = null | ||
| this._parent = null | ||
| this._element = null | ||
| this._triggerArray = null | ||
| this._isTransitioning = null | ||
| } | ||
|
|
||
|
|
||
| // private | ||
|
|
||
| _getConfig(config) { | ||
| config = $.extend({}, Default, config) | ||
| config.toggle = Boolean(config.toggle) // coerce string values | ||
| Util.typeCheckConfig(NAME, config, DefaultType) | ||
| return config | ||
| } | ||
|
|
||
| _getDimension() { | ||
| const hasWidth = $(this._element).hasClass(Dimension.WIDTH) | ||
| return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT | ||
| } | ||
|
|
||
| _getParent() { | ||
| const parent = $(this._config.parent)[0] | ||
| const selector = | ||
| `[data-toggle="collapse"][data-parent="${this._config.parent}"]` | ||
|
|
||
| $(parent).find(selector).each((i, element) => { | ||
| this._addAriaAndCollapsedClass( | ||
| Collapse._getTargetFromElement(element), | ||
| [element] | ||
| ) | ||
| }) | ||
|
|
||
| return parent | ||
| } | ||
|
|
||
| _addAriaAndCollapsedClass(element, triggerArray) { | ||
| if (element) { | ||
| const isOpen = $(element).hasClass(ClassName.SHOW) | ||
| element.setAttribute('aria-expanded', isOpen) | ||
|
|
||
| if (triggerArray.length) { | ||
| $(triggerArray) | ||
| .toggleClass(ClassName.COLLAPSED, !isOpen) | ||
| .attr('aria-expanded', isOpen) | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // static | ||
|
|
||
| static _getTargetFromElement(element) { | ||
| const selector = Util.getSelectorFromElement(element) | ||
| return selector ? $(selector)[0] : null | ||
| } | ||
|
|
||
| static _jQueryInterface(config) { | ||
| return this.each(function () { | ||
| const $this = $(this) | ||
| let data = $this.data(DATA_KEY) | ||
| const _config = $.extend( | ||
| {}, | ||
| Default, | ||
| $this.data(), | ||
| typeof config === 'object' && config | ||
| ) | ||
|
|
||
| if (!data && _config.toggle && /show|hide/.test(config)) { | ||
| _config.toggle = false | ||
| } | ||
|
|
||
| if (!data) { | ||
| data = new Collapse(this, _config) | ||
| $this.data(DATA_KEY, data) | ||
| } | ||
|
|
||
| if (typeof config === 'string') { | ||
| if (data[config] === undefined) { | ||
| throw new Error(`No method named "${config}"`) | ||
| } | ||
| data[config]() | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Data Api implementation | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { | ||
| event.preventDefault() | ||
|
|
||
| const target = Collapse._getTargetFromElement(this) | ||
| const data = $(target).data(DATA_KEY) | ||
| const config = data ? 'toggle' : $(this).data() | ||
|
|
||
| Collapse._jQueryInterface.call($(target), config) | ||
| }) | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * jQuery | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $.fn[NAME] = Collapse._jQueryInterface | ||
| $.fn[NAME].Constructor = Collapse | ||
| $.fn[NAME].noConflict = function () { | ||
| $.fn[NAME] = JQUERY_NO_CONFLICT | ||
| return Collapse._jQueryInterface | ||
| } | ||
|
|
||
| return Collapse | ||
|
|
||
| })(jQuery) | ||
|
|
||
| export default Collapse |
| @@ -0,0 +1,303 @@ | ||
| import Util from './util' | ||
|
|
||
|
|
||
| /** | ||
| * -------------------------------------------------------------------------- | ||
| * Bootstrap (v4.0.0-alpha.6): dropdown.js | ||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||
| * -------------------------------------------------------------------------- | ||
| */ | ||
|
|
||
| const Dropdown = (($) => { | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Constants | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| const NAME = 'dropdown' | ||
| const VERSION = '4.0.0-alpha.6' | ||
| const DATA_KEY = 'bs.dropdown' | ||
| const EVENT_KEY = `.${DATA_KEY}` | ||
| const DATA_API_KEY = '.data-api' | ||
| const JQUERY_NO_CONFLICT = $.fn[NAME] | ||
| const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key | ||
| const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key | ||
| const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key | ||
| const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse) | ||
|
|
||
| const Event = { | ||
| HIDE : `hide${EVENT_KEY}`, | ||
| HIDDEN : `hidden${EVENT_KEY}`, | ||
| SHOW : `show${EVENT_KEY}`, | ||
| SHOWN : `shown${EVENT_KEY}`, | ||
| CLICK : `click${EVENT_KEY}`, | ||
| CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`, | ||
| FOCUSIN_DATA_API : `focusin${EVENT_KEY}${DATA_API_KEY}`, | ||
| KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}` | ||
| } | ||
|
|
||
| const ClassName = { | ||
| BACKDROP : 'dropdown-backdrop', | ||
| DISABLED : 'disabled', | ||
| SHOW : 'show' | ||
| } | ||
|
|
||
| const Selector = { | ||
| BACKDROP : '.dropdown-backdrop', | ||
| DATA_TOGGLE : '[data-toggle="dropdown"]', | ||
| FORM_CHILD : '.dropdown form', | ||
| ROLE_MENU : '[role="menu"]', | ||
| ROLE_LISTBOX : '[role="listbox"]', | ||
| NAVBAR_NAV : '.navbar-nav', | ||
| VISIBLE_ITEMS : '[role="menu"] li:not(.disabled) a, ' | ||
| + '[role="listbox"] li:not(.disabled) a' | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Class Definition | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| class Dropdown { | ||
|
|
||
| constructor(element) { | ||
| this._element = element | ||
|
|
||
| this._addEventListeners() | ||
| } | ||
|
|
||
|
|
||
| // getters | ||
|
|
||
| static get VERSION() { | ||
| return VERSION | ||
| } | ||
|
|
||
|
|
||
| // public | ||
|
|
||
| toggle() { | ||
| if (this.disabled || $(this).hasClass(ClassName.DISABLED)) { | ||
| return false | ||
| } | ||
|
|
||
| const parent = Dropdown._getParentFromElement(this) | ||
| const isActive = $(parent).hasClass(ClassName.SHOW) | ||
|
|
||
| Dropdown._clearMenus() | ||
|
|
||
| if (isActive) { | ||
| return false | ||
| } | ||
|
|
||
| if ('ontouchstart' in document.documentElement && | ||
| !$(parent).closest(Selector.NAVBAR_NAV).length) { | ||
|
|
||
| // if mobile we use a backdrop because click events don't delegate | ||
| const dropdown = document.createElement('div') | ||
| dropdown.className = ClassName.BACKDROP | ||
| $(dropdown).insertBefore(this) | ||
| $(dropdown).on('click', Dropdown._clearMenus) | ||
| } | ||
|
|
||
| const relatedTarget = { | ||
| relatedTarget : this | ||
| } | ||
| const showEvent = $.Event(Event.SHOW, relatedTarget) | ||
|
|
||
| $(parent).trigger(showEvent) | ||
|
|
||
| if (showEvent.isDefaultPrevented()) { | ||
| return false | ||
| } | ||
|
|
||
| this.focus() | ||
| this.setAttribute('aria-expanded', true) | ||
|
|
||
| $(parent).toggleClass(ClassName.SHOW) | ||
| $(parent).trigger($.Event(Event.SHOWN, relatedTarget)) | ||
|
|
||
| return false | ||
| } | ||
|
|
||
| dispose() { | ||
| $.removeData(this._element, DATA_KEY) | ||
| $(this._element).off(EVENT_KEY) | ||
| this._element = null | ||
| } | ||
|
|
||
|
|
||
| // private | ||
|
|
||
| _addEventListeners() { | ||
| $(this._element).on(Event.CLICK, this.toggle) | ||
| } | ||
|
|
||
|
|
||
| // static | ||
|
|
||
| static _jQueryInterface(config) { | ||
| return this.each(function () { | ||
| let data = $(this).data(DATA_KEY) | ||
|
|
||
| if (!data) { | ||
| data = new Dropdown(this) | ||
| $(this).data(DATA_KEY, data) | ||
| } | ||
|
|
||
| if (typeof config === 'string') { | ||
| if (data[config] === undefined) { | ||
| throw new Error(`No method named "${config}"`) | ||
| } | ||
| data[config].call(this) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| static _clearMenus(event) { | ||
| if (event && event.which === RIGHT_MOUSE_BUTTON_WHICH) { | ||
| return | ||
| } | ||
|
|
||
| const backdrop = $(Selector.BACKDROP)[0] | ||
| if (backdrop) { | ||
| backdrop.parentNode.removeChild(backdrop) | ||
| } | ||
|
|
||
| const toggles = $.makeArray($(Selector.DATA_TOGGLE)) | ||
|
|
||
| for (let i = 0; i < toggles.length; i++) { | ||
| const parent = Dropdown._getParentFromElement(toggles[i]) | ||
| const relatedTarget = { | ||
| relatedTarget : toggles[i] | ||
| } | ||
|
|
||
| if (!$(parent).hasClass(ClassName.SHOW)) { | ||
| continue | ||
| } | ||
|
|
||
| if (event && (event.type === 'click' && | ||
| /input|textarea/i.test(event.target.tagName) || event.type === 'focusin') | ||
| && $.contains(parent, event.target)) { | ||
| continue | ||
| } | ||
|
|
||
| const hideEvent = $.Event(Event.HIDE, relatedTarget) | ||
| $(parent).trigger(hideEvent) | ||
| if (hideEvent.isDefaultPrevented()) { | ||
| continue | ||
| } | ||
|
|
||
| toggles[i].setAttribute('aria-expanded', 'false') | ||
|
|
||
| $(parent) | ||
| .removeClass(ClassName.SHOW) | ||
| .trigger($.Event(Event.HIDDEN, relatedTarget)) | ||
| } | ||
| } | ||
|
|
||
| static _getParentFromElement(element) { | ||
| let parent | ||
| const selector = Util.getSelectorFromElement(element) | ||
|
|
||
| if (selector) { | ||
| parent = $(selector)[0] | ||
| } | ||
|
|
||
| return parent || element.parentNode | ||
| } | ||
|
|
||
| static _dataApiKeydownHandler(event) { | ||
| if (!/(38|40|27|32)/.test(event.which) || | ||
| /input|textarea/i.test(event.target.tagName)) { | ||
| return | ||
| } | ||
|
|
||
| event.preventDefault() | ||
| event.stopPropagation() | ||
|
|
||
| if (this.disabled || $(this).hasClass(ClassName.DISABLED)) { | ||
| return | ||
| } | ||
|
|
||
| const parent = Dropdown._getParentFromElement(this) | ||
| const isActive = $(parent).hasClass(ClassName.SHOW) | ||
|
|
||
| if (!isActive && event.which !== ESCAPE_KEYCODE || | ||
| isActive && event.which === ESCAPE_KEYCODE) { | ||
|
|
||
| if (event.which === ESCAPE_KEYCODE) { | ||
| const toggle = $(parent).find(Selector.DATA_TOGGLE)[0] | ||
| $(toggle).trigger('focus') | ||
| } | ||
|
|
||
| $(this).trigger('click') | ||
| return | ||
| } | ||
|
|
||
| const items = $(parent).find(Selector.VISIBLE_ITEMS).get() | ||
|
|
||
| if (!items.length) { | ||
| return | ||
| } | ||
|
|
||
| let index = items.indexOf(event.target) | ||
|
|
||
| if (event.which === ARROW_UP_KEYCODE && index > 0) { // up | ||
| index-- | ||
| } | ||
|
|
||
| if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // down | ||
| index++ | ||
| } | ||
|
|
||
| if (index < 0) { | ||
| index = 0 | ||
| } | ||
|
|
||
| items[index].focus() | ||
| } | ||
|
|
||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Data Api implementation | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $(document) | ||
| .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) | ||
| .on(Event.KEYDOWN_DATA_API, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler) | ||
| .on(Event.KEYDOWN_DATA_API, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler) | ||
| .on(`${Event.CLICK_DATA_API} ${Event.FOCUSIN_DATA_API}`, Dropdown._clearMenus) | ||
| .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle) | ||
| .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => { | ||
| e.stopPropagation() | ||
| }) | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * jQuery | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $.fn[NAME] = Dropdown._jQueryInterface | ||
| $.fn[NAME].Constructor = Dropdown | ||
| $.fn[NAME].noConflict = function () { | ||
| $.fn[NAME] = JQUERY_NO_CONFLICT | ||
| return Dropdown._jQueryInterface | ||
| } | ||
|
|
||
| return Dropdown | ||
|
|
||
| })(jQuery) | ||
|
|
||
| export default Dropdown |
| @@ -0,0 +1,179 @@ | ||
| import Tooltip from './tooltip' | ||
|
|
||
|
|
||
| /** | ||
| * -------------------------------------------------------------------------- | ||
| * Bootstrap (v4.0.0-alpha.6): popover.js | ||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||
| * -------------------------------------------------------------------------- | ||
| */ | ||
|
|
||
| const Popover = (($) => { | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Constants | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| const NAME = 'popover' | ||
| const VERSION = '4.0.0-alpha.6' | ||
| const DATA_KEY = 'bs.popover' | ||
| const EVENT_KEY = `.${DATA_KEY}` | ||
| const JQUERY_NO_CONFLICT = $.fn[NAME] | ||
|
|
||
| const Default = $.extend({}, Tooltip.Default, { | ||
| placement : 'right', | ||
| trigger : 'click', | ||
| content : '', | ||
| template : '<div class="popover" role="tooltip">' | ||
| + '<h3 class="popover-title"></h3>' | ||
| + '<div class="popover-content"></div></div>' | ||
| }) | ||
|
|
||
| const DefaultType = $.extend({}, Tooltip.DefaultType, { | ||
| content : '(string|element|function)' | ||
| }) | ||
|
|
||
| const ClassName = { | ||
| FADE : 'fade', | ||
| SHOW : 'show' | ||
| } | ||
|
|
||
| const Selector = { | ||
| TITLE : '.popover-title', | ||
| CONTENT : '.popover-content' | ||
| } | ||
|
|
||
| const Event = { | ||
| HIDE : `hide${EVENT_KEY}`, | ||
| HIDDEN : `hidden${EVENT_KEY}`, | ||
| SHOW : `show${EVENT_KEY}`, | ||
| SHOWN : `shown${EVENT_KEY}`, | ||
| INSERTED : `inserted${EVENT_KEY}`, | ||
| CLICK : `click${EVENT_KEY}`, | ||
| FOCUSIN : `focusin${EVENT_KEY}`, | ||
| FOCUSOUT : `focusout${EVENT_KEY}`, | ||
| MOUSEENTER : `mouseenter${EVENT_KEY}`, | ||
| MOUSELEAVE : `mouseleave${EVENT_KEY}` | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Class Definition | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| class Popover extends Tooltip { | ||
|
|
||
|
|
||
| // getters | ||
|
|
||
| static get VERSION() { | ||
| return VERSION | ||
| } | ||
|
|
||
| static get Default() { | ||
| return Default | ||
| } | ||
|
|
||
| static get NAME() { | ||
| return NAME | ||
| } | ||
|
|
||
| static get DATA_KEY() { | ||
| return DATA_KEY | ||
| } | ||
|
|
||
| static get Event() { | ||
| return Event | ||
| } | ||
|
|
||
| static get EVENT_KEY() { | ||
| return EVENT_KEY | ||
| } | ||
|
|
||
| static get DefaultType() { | ||
| return DefaultType | ||
| } | ||
|
|
||
|
|
||
| // overrides | ||
|
|
||
| isWithContent() { | ||
| return this.getTitle() || this._getContent() | ||
| } | ||
|
|
||
| getTipElement() { | ||
| return this.tip = this.tip || $(this.config.template)[0] | ||
| } | ||
|
|
||
| setContent() { | ||
| const $tip = $(this.getTipElement()) | ||
|
|
||
| // we use append for html objects to maintain js events | ||
| this.setElementContent($tip.find(Selector.TITLE), this.getTitle()) | ||
| this.setElementContent($tip.find(Selector.CONTENT), this._getContent()) | ||
|
|
||
| $tip.removeClass(`${ClassName.FADE} ${ClassName.SHOW}`) | ||
|
|
||
| this.cleanupTether() | ||
| } | ||
|
|
||
| // private | ||
|
|
||
| _getContent() { | ||
| return this.element.getAttribute('data-content') | ||
| || (typeof this.config.content === 'function' ? | ||
| this.config.content.call(this.element) : | ||
| this.config.content) | ||
| } | ||
|
|
||
|
|
||
| // static | ||
|
|
||
| static _jQueryInterface(config) { | ||
| return this.each(function () { | ||
| let data = $(this).data(DATA_KEY) | ||
| const _config = typeof config === 'object' ? config : null | ||
|
|
||
| if (!data && /destroy|hide/.test(config)) { | ||
| return | ||
| } | ||
|
|
||
| if (!data) { | ||
| data = new Popover(this, _config) | ||
| $(this).data(DATA_KEY, data) | ||
| } | ||
|
|
||
| if (typeof config === 'string') { | ||
| if (data[config] === undefined) { | ||
| throw new Error(`No method named "${config}"`) | ||
| } | ||
| data[config]() | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * jQuery | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $.fn[NAME] = Popover._jQueryInterface | ||
| $.fn[NAME].Constructor = Popover | ||
| $.fn[NAME].noConflict = function () { | ||
| $.fn[NAME] = JQUERY_NO_CONFLICT | ||
| return Popover._jQueryInterface | ||
| } | ||
|
|
||
| return Popover | ||
|
|
||
| })(jQuery) | ||
|
|
||
| export default Popover |
| @@ -0,0 +1,332 @@ | ||
| import Util from './util' | ||
|
|
||
|
|
||
| /** | ||
| * -------------------------------------------------------------------------- | ||
| * Bootstrap (v4.0.0-alpha.6): scrollspy.js | ||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||
| * -------------------------------------------------------------------------- | ||
| */ | ||
|
|
||
| const ScrollSpy = (($) => { | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Constants | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| const NAME = 'scrollspy' | ||
| const VERSION = '4.0.0-alpha.6' | ||
| const DATA_KEY = 'bs.scrollspy' | ||
| const EVENT_KEY = `.${DATA_KEY}` | ||
| const DATA_API_KEY = '.data-api' | ||
| const JQUERY_NO_CONFLICT = $.fn[NAME] | ||
|
|
||
| const Default = { | ||
| offset : 10, | ||
| method : 'auto', | ||
| target : '' | ||
| } | ||
|
|
||
| const DefaultType = { | ||
| offset : 'number', | ||
| method : 'string', | ||
| target : '(string|element)' | ||
| } | ||
|
|
||
| const Event = { | ||
| ACTIVATE : `activate${EVENT_KEY}`, | ||
| SCROLL : `scroll${EVENT_KEY}`, | ||
| LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}` | ||
| } | ||
|
|
||
| const ClassName = { | ||
| DROPDOWN_ITEM : 'dropdown-item', | ||
| DROPDOWN_MENU : 'dropdown-menu', | ||
| NAV_LINK : 'nav-link', | ||
| NAV : 'nav', | ||
| ACTIVE : 'active' | ||
| } | ||
|
|
||
| const Selector = { | ||
| DATA_SPY : '[data-spy="scroll"]', | ||
| ACTIVE : '.active', | ||
| LIST_ITEM : '.list-item', | ||
| LI : 'li', | ||
| LI_DROPDOWN : 'li.dropdown', | ||
| NAV_LINKS : '.nav-link', | ||
| DROPDOWN : '.dropdown', | ||
| DROPDOWN_ITEMS : '.dropdown-item', | ||
| DROPDOWN_TOGGLE : '.dropdown-toggle' | ||
| } | ||
|
|
||
| const OffsetMethod = { | ||
| OFFSET : 'offset', | ||
| POSITION : 'position' | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Class Definition | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| class ScrollSpy { | ||
|
|
||
| constructor(element, config) { | ||
| this._element = element | ||
| this._scrollElement = element.tagName === 'BODY' ? window : element | ||
| this._config = this._getConfig(config) | ||
| this._selector = `${this._config.target} ${Selector.NAV_LINKS},` | ||
| + `${this._config.target} ${Selector.DROPDOWN_ITEMS}` | ||
| this._offsets = [] | ||
| this._targets = [] | ||
| this._activeTarget = null | ||
| this._scrollHeight = 0 | ||
|
|
||
| $(this._scrollElement).on(Event.SCROLL, (event) => this._process(event)) | ||
|
|
||
| this.refresh() | ||
| this._process() | ||
| } | ||
|
|
||
|
|
||
| // getters | ||
|
|
||
| static get VERSION() { | ||
| return VERSION | ||
| } | ||
|
|
||
| static get Default() { | ||
| return Default | ||
| } | ||
|
|
||
|
|
||
| // public | ||
|
|
||
| refresh() { | ||
| const autoMethod = this._scrollElement !== this._scrollElement.window ? | ||
| OffsetMethod.POSITION : OffsetMethod.OFFSET | ||
|
|
||
| const offsetMethod = this._config.method === 'auto' ? | ||
| autoMethod : this._config.method | ||
|
|
||
| const offsetBase = offsetMethod === OffsetMethod.POSITION ? | ||
| this._getScrollTop() : 0 | ||
|
|
||
| this._offsets = [] | ||
| this._targets = [] | ||
|
|
||
| this._scrollHeight = this._getScrollHeight() | ||
|
|
||
| const targets = $.makeArray($(this._selector)) | ||
|
|
||
| targets | ||
| .map((element) => { | ||
| let target | ||
| const targetSelector = Util.getSelectorFromElement(element) | ||
|
|
||
| if (targetSelector) { | ||
| target = $(targetSelector)[0] | ||
| } | ||
|
|
||
| if (target && (target.offsetWidth || target.offsetHeight)) { | ||
| // todo (fat): remove sketch reliance on jQuery position/offset | ||
| return [ | ||
| $(target)[offsetMethod]().top + offsetBase, | ||
| targetSelector | ||
| ] | ||
| } | ||
| return null | ||
| }) | ||
| .filter((item) => item) | ||
| .sort((a, b) => a[0] - b[0]) | ||
| .forEach((item) => { | ||
| this._offsets.push(item[0]) | ||
| this._targets.push(item[1]) | ||
| }) | ||
| } | ||
|
|
||
| dispose() { | ||
| $.removeData(this._element, DATA_KEY) | ||
| $(this._scrollElement).off(EVENT_KEY) | ||
|
|
||
| this._element = null | ||
| this._scrollElement = null | ||
| this._config = null | ||
| this._selector = null | ||
| this._offsets = null | ||
| this._targets = null | ||
| this._activeTarget = null | ||
| this._scrollHeight = null | ||
| } | ||
|
|
||
|
|
||
| // private | ||
|
|
||
| _getConfig(config) { | ||
| config = $.extend({}, Default, config) | ||
|
|
||
| if (typeof config.target !== 'string') { | ||
| let id = $(config.target).attr('id') | ||
| if (!id) { | ||
| id = Util.getUID(NAME) | ||
| $(config.target).attr('id', id) | ||
| } | ||
| config.target = `#${id}` | ||
| } | ||
|
|
||
| Util.typeCheckConfig(NAME, config, DefaultType) | ||
|
|
||
| return config | ||
| } | ||
|
|
||
| _getScrollTop() { | ||
| return this._scrollElement === window ? | ||
| this._scrollElement.pageYOffset : this._scrollElement.scrollTop | ||
| } | ||
|
|
||
| _getScrollHeight() { | ||
| return this._scrollElement.scrollHeight || Math.max( | ||
| document.body.scrollHeight, | ||
| document.documentElement.scrollHeight | ||
| ) | ||
| } | ||
|
|
||
| _getOffsetHeight() { | ||
| return this._scrollElement === window ? | ||
| window.innerHeight : this._scrollElement.offsetHeight | ||
| } | ||
|
|
||
| _process() { | ||
| const scrollTop = this._getScrollTop() + this._config.offset | ||
| const scrollHeight = this._getScrollHeight() | ||
| const maxScroll = this._config.offset | ||
| + scrollHeight | ||
| - this._getOffsetHeight() | ||
|
|
||
| if (this._scrollHeight !== scrollHeight) { | ||
| this.refresh() | ||
| } | ||
|
|
||
| if (scrollTop >= maxScroll) { | ||
| const target = this._targets[this._targets.length - 1] | ||
|
|
||
| if (this._activeTarget !== target) { | ||
| this._activate(target) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) { | ||
| this._activeTarget = null | ||
| this._clear() | ||
| return | ||
| } | ||
|
|
||
| for (let i = this._offsets.length; i--;) { | ||
| const isActiveTarget = this._activeTarget !== this._targets[i] | ||
| && scrollTop >= this._offsets[i] | ||
| && (this._offsets[i + 1] === undefined || | ||
| scrollTop < this._offsets[i + 1]) | ||
|
|
||
| if (isActiveTarget) { | ||
| this._activate(this._targets[i]) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| _activate(target) { | ||
| this._activeTarget = target | ||
|
|
||
| this._clear() | ||
|
|
||
| let queries = this._selector.split(',') | ||
| queries = queries.map((selector) => { | ||
| return `${selector}[data-target="${target}"],` + | ||
| `${selector}[href="${target}"]` | ||
| }) | ||
|
|
||
| const $link = $(queries.join(',')) | ||
|
|
||
| if ($link.hasClass(ClassName.DROPDOWN_ITEM)) { | ||
| $link.closest(Selector.DROPDOWN).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE) | ||
| $link.addClass(ClassName.ACTIVE) | ||
| } else { | ||
| // todo (fat) this is kinda sus... | ||
| // recursively add actives to tested nav-links | ||
| $link.parents(Selector.LI).find(`> ${Selector.NAV_LINKS}`).addClass(ClassName.ACTIVE) | ||
| } | ||
|
|
||
| $(this._scrollElement).trigger(Event.ACTIVATE, { | ||
| relatedTarget: target | ||
| }) | ||
| } | ||
|
|
||
| _clear() { | ||
| $(this._selector).filter(Selector.ACTIVE).removeClass(ClassName.ACTIVE) | ||
| } | ||
|
|
||
|
|
||
| // static | ||
|
|
||
| static _jQueryInterface(config) { | ||
| return this.each(function () { | ||
| let data = $(this).data(DATA_KEY) | ||
| const _config = typeof config === 'object' && config | ||
|
|
||
| if (!data) { | ||
| data = new ScrollSpy(this, _config) | ||
| $(this).data(DATA_KEY, data) | ||
| } | ||
|
|
||
| if (typeof config === 'string') { | ||
| if (data[config] === undefined) { | ||
| throw new Error(`No method named "${config}"`) | ||
| } | ||
| data[config]() | ||
| } | ||
| }) | ||
| } | ||
|
|
||
|
|
||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Data Api implementation | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $(window).on(Event.LOAD_DATA_API, () => { | ||
| const scrollSpys = $.makeArray($(Selector.DATA_SPY)) | ||
|
|
||
| for (let i = scrollSpys.length; i--;) { | ||
| const $spy = $(scrollSpys[i]) | ||
| ScrollSpy._jQueryInterface.call($spy, $spy.data()) | ||
| } | ||
| }) | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * jQuery | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $.fn[NAME] = ScrollSpy._jQueryInterface | ||
| $.fn[NAME].Constructor = ScrollSpy | ||
| $.fn[NAME].noConflict = function () { | ||
| $.fn[NAME] = JQUERY_NO_CONFLICT | ||
| return ScrollSpy._jQueryInterface | ||
| } | ||
|
|
||
| return ScrollSpy | ||
|
|
||
| })(jQuery) | ||
|
|
||
| export default ScrollSpy |
| @@ -0,0 +1,278 @@ | ||
| import Util from './util' | ||
|
|
||
|
|
||
| /** | ||
| * -------------------------------------------------------------------------- | ||
| * Bootstrap (v4.0.0-alpha.6): tab.js | ||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||
| * -------------------------------------------------------------------------- | ||
| */ | ||
|
|
||
| const Tab = (($) => { | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Constants | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| const NAME = 'tab' | ||
| const VERSION = '4.0.0-alpha.6' | ||
| const DATA_KEY = 'bs.tab' | ||
| const EVENT_KEY = `.${DATA_KEY}` | ||
| const DATA_API_KEY = '.data-api' | ||
| const JQUERY_NO_CONFLICT = $.fn[NAME] | ||
| const TRANSITION_DURATION = 150 | ||
|
|
||
| const Event = { | ||
| HIDE : `hide${EVENT_KEY}`, | ||
| HIDDEN : `hidden${EVENT_KEY}`, | ||
| SHOW : `show${EVENT_KEY}`, | ||
| SHOWN : `shown${EVENT_KEY}`, | ||
| CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` | ||
| } | ||
|
|
||
| const ClassName = { | ||
| DROPDOWN_MENU : 'dropdown-menu', | ||
| ACTIVE : 'active', | ||
| DISABLED : 'disabled', | ||
| FADE : 'fade', | ||
| SHOW : 'show' | ||
| } | ||
|
|
||
| const Selector = { | ||
| A : 'a', | ||
| LI : 'li', | ||
| DROPDOWN : '.dropdown', | ||
| LIST : 'ul:not(.dropdown-menu), ol:not(.dropdown-menu), nav:not(.dropdown-menu)', | ||
| FADE_CHILD : '> .nav-item .fade, > .fade', | ||
| ACTIVE : '.active', | ||
| ACTIVE_CHILD : '> .nav-item > .active, > .active', | ||
| DATA_TOGGLE : '[data-toggle="tab"], [data-toggle="pill"]', | ||
| DROPDOWN_TOGGLE : '.dropdown-toggle', | ||
| DROPDOWN_ACTIVE_CHILD : '> .dropdown-menu .active' | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Class Definition | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| class Tab { | ||
|
|
||
| constructor(element) { | ||
| this._element = element | ||
| } | ||
|
|
||
|
|
||
| // getters | ||
|
|
||
| static get VERSION() { | ||
| return VERSION | ||
| } | ||
|
|
||
|
|
||
| // public | ||
|
|
||
| show() { | ||
| if (this._element.parentNode && | ||
| this._element.parentNode.nodeType === Node.ELEMENT_NODE && | ||
| $(this._element).hasClass(ClassName.ACTIVE) || | ||
| $(this._element).hasClass(ClassName.DISABLED)) { | ||
| return | ||
| } | ||
|
|
||
| let target | ||
| let previous | ||
| const listElement = $(this._element).closest(Selector.LIST)[0] | ||
| const selector = Util.getSelectorFromElement(this._element) | ||
|
|
||
| if (listElement) { | ||
| previous = $.makeArray($(listElement).find(Selector.ACTIVE)) | ||
| previous = previous[previous.length - 1] | ||
| } | ||
|
|
||
| const hideEvent = $.Event(Event.HIDE, { | ||
| relatedTarget: this._element | ||
| }) | ||
|
|
||
| const showEvent = $.Event(Event.SHOW, { | ||
| relatedTarget: previous | ||
| }) | ||
|
|
||
| if (previous) { | ||
| $(previous).trigger(hideEvent) | ||
| } | ||
|
|
||
| $(this._element).trigger(showEvent) | ||
|
|
||
| if (showEvent.isDefaultPrevented() || | ||
| hideEvent.isDefaultPrevented()) { | ||
| return | ||
| } | ||
|
|
||
| if (selector) { | ||
| target = $(selector)[0] | ||
| } | ||
|
|
||
| this._activate( | ||
| this._element, | ||
| listElement | ||
| ) | ||
|
|
||
| const complete = () => { | ||
| const hiddenEvent = $.Event(Event.HIDDEN, { | ||
| relatedTarget: this._element | ||
| }) | ||
|
|
||
| const shownEvent = $.Event(Event.SHOWN, { | ||
| relatedTarget: previous | ||
| }) | ||
|
|
||
| $(previous).trigger(hiddenEvent) | ||
| $(this._element).trigger(shownEvent) | ||
| } | ||
|
|
||
| if (target) { | ||
| this._activate(target, target.parentNode, complete) | ||
| } else { | ||
| complete() | ||
| } | ||
| } | ||
|
|
||
| dispose() { | ||
| $.removeClass(this._element, DATA_KEY) | ||
| this._element = null | ||
| } | ||
|
|
||
|
|
||
| // private | ||
|
|
||
| _activate(element, container, callback) { | ||
| const active = $(container).find(Selector.ACTIVE_CHILD)[0] | ||
| const isTransitioning = callback | ||
| && Util.supportsTransitionEnd() | ||
| && (active && $(active).hasClass(ClassName.FADE) | ||
| || Boolean($(container).find(Selector.FADE_CHILD)[0])) | ||
|
|
||
| const complete = () => this._transitionComplete( | ||
| element, | ||
| active, | ||
| isTransitioning, | ||
| callback | ||
| ) | ||
|
|
||
| if (active && isTransitioning) { | ||
| $(active) | ||
| .one(Util.TRANSITION_END, complete) | ||
| .emulateTransitionEnd(TRANSITION_DURATION) | ||
|
|
||
| } else { | ||
| complete() | ||
| } | ||
|
|
||
| if (active) { | ||
| $(active).removeClass(ClassName.SHOW) | ||
| } | ||
| } | ||
|
|
||
| _transitionComplete(element, active, isTransitioning, callback) { | ||
| if (active) { | ||
| $(active).removeClass(ClassName.ACTIVE) | ||
|
|
||
| const dropdownChild = $(active.parentNode).find( | ||
| Selector.DROPDOWN_ACTIVE_CHILD | ||
| )[0] | ||
|
|
||
| if (dropdownChild) { | ||
| $(dropdownChild).removeClass(ClassName.ACTIVE) | ||
| } | ||
|
|
||
| active.setAttribute('aria-expanded', false) | ||
| } | ||
|
|
||
| $(element).addClass(ClassName.ACTIVE) | ||
| element.setAttribute('aria-expanded', true) | ||
|
|
||
| if (isTransitioning) { | ||
| Util.reflow(element) | ||
| $(element).addClass(ClassName.SHOW) | ||
| } else { | ||
| $(element).removeClass(ClassName.FADE) | ||
| } | ||
|
|
||
| if (element.parentNode && | ||
| $(element.parentNode).hasClass(ClassName.DROPDOWN_MENU)) { | ||
|
|
||
| const dropdownElement = $(element).closest(Selector.DROPDOWN)[0] | ||
| if (dropdownElement) { | ||
| $(dropdownElement).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE) | ||
| } | ||
|
|
||
| element.setAttribute('aria-expanded', true) | ||
| } | ||
|
|
||
| if (callback) { | ||
| callback() | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // static | ||
|
|
||
| static _jQueryInterface(config) { | ||
| return this.each(function () { | ||
| const $this = $(this) | ||
| let data = $this.data(DATA_KEY) | ||
|
|
||
| if (!data) { | ||
| data = new Tab(this) | ||
| $this.data(DATA_KEY, data) | ||
| } | ||
|
|
||
| if (typeof config === 'string') { | ||
| if (data[config] === undefined) { | ||
| throw new Error(`No method named "${config}"`) | ||
| } | ||
| data[config]() | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| } | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Data Api implementation | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $(document) | ||
| .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { | ||
| event.preventDefault() | ||
| Tab._jQueryInterface.call($(this), 'show') | ||
| }) | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * jQuery | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| $.fn[NAME] = Tab._jQueryInterface | ||
| $.fn[NAME].Constructor = Tab | ||
| $.fn[NAME].noConflict = function () { | ||
| $.fn[NAME] = JQUERY_NO_CONFLICT | ||
| return Tab._jQueryInterface | ||
| } | ||
|
|
||
| return Tab | ||
|
|
||
| })(jQuery) | ||
|
|
||
| export default Tab |
| @@ -0,0 +1,161 @@ | ||
| /** | ||
| * -------------------------------------------------------------------------- | ||
| * Bootstrap (v4.0.0-alpha.6): util.js | ||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||
| * -------------------------------------------------------------------------- | ||
| */ | ||
|
|
||
| const Util = (($) => { | ||
|
|
||
|
|
||
| /** | ||
| * ------------------------------------------------------------------------ | ||
| * Private TransitionEnd Helpers | ||
| * ------------------------------------------------------------------------ | ||
| */ | ||
|
|
||
| let transition = false | ||
|
|
||
| const MAX_UID = 1000000 | ||
|
|
||
| const TransitionEndEvent = { | ||
| WebkitTransition : 'webkitTransitionEnd', | ||
| MozTransition : 'transitionend', | ||
| OTransition : 'oTransitionEnd otransitionend', | ||
| transition : 'transitionend' | ||
| } | ||
|
|
||
| // shoutout AngusCroll (https://goo.gl/pxwQGp) | ||
| function toType(obj) { | ||
| return {}.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase() | ||
| } | ||
|
|
||
| function isElement(obj) { | ||
| return (obj[0] || obj).nodeType | ||
| } | ||
|
|
||
| function getSpecialTransitionEndEvent() { | ||
| return { | ||
| bindType: transition.end, | ||
| delegateType: transition.end, | ||
| handle(event) { | ||
| if ($(event.target).is(this)) { | ||
| return event.handleObj.handler.apply(this, arguments) // eslint-disable-line prefer-rest-params | ||
| } | ||
| return undefined | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function transitionEndTest() { | ||
| if (window.QUnit) { | ||
| return false | ||
| } | ||
|
|
||
| const el = document.createElement('bootstrap') | ||
|
|
||
| for (const name in TransitionEndEvent) { | ||
| if (el.style[name] !== undefined) { | ||
| return { | ||
| end: TransitionEndEvent[name] | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return false | ||
| } | ||
|
|
||
| function transitionEndEmulator(duration) { | ||
| let called = false | ||
|
|
||
| $(this).one(Util.TRANSITION_END, () => { | ||
| called = true | ||
| }) | ||
|
|
||
| setTimeout(() => { | ||
| if (!called) { | ||
| Util.triggerTransitionEnd(this) | ||
| } | ||
| }, duration) | ||
|
|
||
| return this | ||
| } | ||
|
|
||
| function setTransitionEndSupport() { | ||
| transition = transitionEndTest() | ||
|
|
||
| $.fn.emulateTransitionEnd = transitionEndEmulator | ||
|
|
||
| if (Util.supportsTransitionEnd()) { | ||
| $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent() | ||
| } | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * -------------------------------------------------------------------------- | ||
| * Public Util Api | ||
| * -------------------------------------------------------------------------- | ||
| */ | ||
|
|
||
| const Util = { | ||
|
|
||
| TRANSITION_END: 'bsTransitionEnd', | ||
|
|
||
| getUID(prefix) { | ||
| do { | ||
| // eslint-disable-next-line no-bitwise | ||
| prefix += ~~(Math.random() * MAX_UID) // "~~" acts like a faster Math.floor() here | ||
| } while (document.getElementById(prefix)) | ||
| return prefix | ||
| }, | ||
|
|
||
| getSelectorFromElement(element) { | ||
| let selector = element.getAttribute('data-target') | ||
|
|
||
| if (!selector) { | ||
| selector = element.getAttribute('href') || '' | ||
| selector = /^#[a-z]/i.test(selector) ? selector : null | ||
| } | ||
|
|
||
| return selector | ||
| }, | ||
|
|
||
| reflow(element) { | ||
| return element.offsetHeight | ||
| }, | ||
|
|
||
| triggerTransitionEnd(element) { | ||
| $(element).trigger(transition.end) | ||
| }, | ||
|
|
||
| supportsTransitionEnd() { | ||
| return Boolean(transition) | ||
| }, | ||
|
|
||
| typeCheckConfig(componentName, config, configTypes) { | ||
| for (const property in configTypes) { | ||
| if (configTypes.hasOwnProperty(property)) { | ||
| const expectedTypes = configTypes[property] | ||
| const value = config[property] | ||
| const valueType = value && isElement(value) ? | ||
| 'element' : toType(value) | ||
|
|
||
| if (!new RegExp(expectedTypes).test(valueType)) { | ||
| throw new Error( | ||
| `${componentName.toUpperCase()}: ` + | ||
| `Option "${property}" provided type "${valueType}" ` + | ||
| `but expected type "${expectedTypes}".`) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| setTransitionEndSupport() | ||
|
|
||
| return Util | ||
|
|
||
| })(jQuery) | ||
|
|
||
| export default Util |
| @@ -0,0 +1,18 @@ | ||
| # set env vars usually set by MyGet (enable for local testing) | ||
| #$env:SourcesPath = '..' | ||
| #$env:NuGet = "./nuget.exe" #https://dist.nuget.org/win-x86-commandline/latest/nuget.exe | ||
|
|
||
| $nuget = $env:NuGet | ||
|
|
||
| # parse the version number out of package.json | ||
| $bsversionParts = ((Get-Content $env:SourcesPath\package.json) -join "`n" | ConvertFrom-Json).version.split('-', 2) # split the version on the '-' | ||
| $bsversion = $bsversionParts[0] | ||
|
|
||
| if ($bsversionParts.Length -gt 1) | ||
| { | ||
| $bsversion += '-' + $bsversionParts[1].replace('.', '').replace('-', '_') # strip out invalid chars from the PreRelease part | ||
| } | ||
|
|
||
| # create packages | ||
| & $nuget pack "$env:SourcesPath\nuget\bootstrap.nuspec" -Verbosity detailed -NonInteractive -NoPackageAnalysis -BasePath $env:SourcesPath -Version $bsversion | ||
| & $nuget pack "$env:SourcesPath\nuget\bootstrap.sass.nuspec" -Verbosity detailed -NonInteractive -NoPackageAnalysis -BasePath $env:SourcesPath -Version $bsversion |
| @@ -0,0 +1,27 @@ | ||
| <?xml version="1.0"?> | ||
| <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> | ||
| <metadata> | ||
| <id>bootstrap</id> | ||
| <version>4.0.0</version> | ||
| <title>Bootstrap CSS</title> | ||
| <authors>The Bootstrap Authors, Twitter Inc.</authors> | ||
| <owners>bootstrap</owners> | ||
| <description>The most popular front-end framework for developing responsive, mobile first projects on the web.</description> | ||
| <releaseNotes>https://blog.getbootstrap.com</releaseNotes> | ||
| <summary>Bootstrap framework in CSS. Includes fonts and JavaScript</summary> | ||
| <language>en-us</language> | ||
| <projectUrl>https://getbootstrap.com</projectUrl> | ||
| <iconUrl>https://getbootstrap.com/apple-touch-icon.png</iconUrl> | ||
| <licenseUrl>https://github.com/twbs/bootstrap/blob/master/LICENSE</licenseUrl> | ||
| <copyright>Copyright 2017</copyright> | ||
| <requireLicenseAcceptance>false</requireLicenseAcceptance> | ||
| <dependencies> | ||
| <dependency id="jQuery" version="[1.9.1,4)" /> | ||
| </dependencies> | ||
| <tags>css mobile-first responsive front-end framework web</tags> | ||
| </metadata> | ||
| <files> | ||
| <file src="dist\css\*.*" target="content\Content" /> | ||
| <file src="dist\js\bootstrap*.js" target="content\Scripts" /> | ||
| </files> | ||
| </package> |
| @@ -0,0 +1,27 @@ | ||
| <?xml version="1.0"?> | ||
| <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> | ||
| <metadata> | ||
| <id>bootstrap.sass</id> | ||
| <version>4.0.0</version> | ||
| <title>Bootstrap Sass</title> | ||
| <authors>The Bootstrap Authors, Twitter Inc.</authors> | ||
| <owners>bootstrap</owners> | ||
| <description>The most popular front-end framework for developing responsive, mobile first projects on the web.</description> | ||
| <releaseNotes>https://blog.getbootstrap.com</releaseNotes> | ||
| <summary>Bootstrap framework in Sass. Includes fonts and JavaScript</summary> | ||
| <language>en-us</language> | ||
| <projectUrl>https://getbootstrap.com</projectUrl> | ||
| <iconUrl>https://getbootstrap.com/apple-touch-icon.png</iconUrl> | ||
| <licenseUrl>https://github.com/twbs/bootstrap/blob/master/LICENSE</licenseUrl> | ||
| <copyright>Copyright 2017</copyright> | ||
| <requireLicenseAcceptance>false</requireLicenseAcceptance> | ||
| <dependencies> | ||
| <dependency id="jQuery" version="[1.9.1,4)" /> | ||
| </dependencies> | ||
| <tags>css sass mobile-first responsive front-end framework web</tags> | ||
| </metadata> | ||
| <files> | ||
| <file src="scss\**\*.scss" target="content\Content\bootstrap" /> | ||
| <file src="dist\js\bootstrap*.js" target="content\Scripts" /> | ||
| </files> | ||
| </package> |
| @@ -0,0 +1,19 @@ | ||
| // package metadata file for Meteor.js | ||
|
|
||
| /* global Package:true */ | ||
|
|
||
| Package.describe({ | ||
| name: 'twbs:bootstrap', // https://atmospherejs.com/twbs/bootstrap | ||
| summary: 'The most popular front-end framework for developing responsive, mobile first projects on the web.', | ||
| version: '4.0.0-alpha.6', | ||
| git: 'https://github.com/twbs/bootstrap.git' | ||
| }); | ||
|
|
||
| Package.onUse(function (api) { | ||
| api.versionsFrom('METEOR@1.0'); | ||
| api.use('jquery', 'client'); | ||
| api.addFiles([ | ||
| 'dist/css/bootstrap.css', | ||
| 'dist/js/bootstrap.js' | ||
| ], 'client'); | ||
| }); |
| @@ -0,0 +1,112 @@ | ||
| { | ||
| "name": "bootstrap", | ||
| "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", | ||
| "version": "4.0.0-alpha.6", | ||
| "keywords": [ | ||
| "css", | ||
| "sass", | ||
| "mobile-first", | ||
| "responsive", | ||
| "front-end", | ||
| "framework", | ||
| "web" | ||
| ], | ||
| "homepage": "https://getbootstrap.com", | ||
| "author": "The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)", | ||
| "contributors": [ | ||
| "Twitter, Inc." | ||
| ], | ||
| "scripts": { | ||
| "change-version": "node grunt/change-version.js", | ||
| "clean-css": "cleancss --skip-advanced --source-map --output dist/css/bootstrap.min.css dist/css/bootstrap.css && cleancss --skip-advanced --source-map --output dist/css/bootstrap-grid.min.css dist/css/bootstrap-grid.css && cleancss --skip-advanced --source-map --output dist/css/bootstrap-reboot.min.css dist/css/bootstrap-reboot.css", | ||
| "clean-css-docs": "cleancss --skip-advanced --source-map --output docs/assets/css/docs.min.css docs/assets/css/docs.min.css", | ||
| "eslint": "eslint --ignore-path .eslintignore js && eslint --config js/tests/.eslintrc.json --env node grunt Gruntfile.js && eslint --config js/tests/.eslintrc.json docs/assets/js/src docs/assets/js/ie-emulation-modes-warning.js docs/assets/js/ie10-viewport-bug-workaround.js", | ||
| "htmlhint": "htmlhint --config docs/.htmlhintrc _gh_pages/", | ||
| "postcss": "postcss --config grunt/postcss.js --replace dist/css/*.css", | ||
| "postcss-docs": "postcss --config grunt/postcss.js --no-map --replace docs/assets/css/docs.min.css && postcss --config grunt/postcss.js --no-map --replace docs/examples/**/*.css", | ||
| "sass": "node-sass --output-style expanded --source-map true --precision 6 scss/bootstrap.scss dist/css/bootstrap.css && node-sass --output-style expanded --source-map true --precision 6 scss/bootstrap-grid.scss dist/css/bootstrap-grid.css && node-sass --output-style expanded --source-map true --precision 6 scss/bootstrap-reboot.scss dist/css/bootstrap-reboot.css", | ||
| "sass-docs": "node-sass --output-style expanded --source-map true --precision 6 docs/assets/scss/docs.scss docs/assets/css/docs.min.css", | ||
| "scss-lint": "bundle exec scss-lint --config scss/.scss-lint.yml --exclude scss/_normalize.scss scss/*.scss", | ||
| "scss-lint-docs": "bundle exec scss-lint --config scss/.scss-lint.yml --exclude docs/assets/scss/docs.scss docs/assets/scss/*.scss", | ||
| "uglify": "uglifyjs --compress warnings=false --mangle --comments '/^!/' --output dist/js/bootstrap.min.js dist/js/bootstrap.js", | ||
| "uglify-docs": "uglifyjs --compress warnings=false --mangle --comments '/^!/' --output docs/assets/js/docs.min.js docs/assets/js/vendor/*.js docs/assets/js/src/application.js", | ||
| "update-shrinkwrap": "npm shrinkwrap --dev && shx mv ./npm-shrinkwrap.json ./grunt/npm-shrinkwrap.json", | ||
| "test": "npm run eslint && grunt test" | ||
| }, | ||
| "style": "dist/css/bootstrap.css", | ||
| "sass": "scss/bootstrap.scss", | ||
| "main": "dist/js/bootstrap", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/twbs/bootstrap.git" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/twbs/bootstrap/issues" | ||
| }, | ||
| "license": "MIT", | ||
| "dependencies": { | ||
| "jquery": ">=1.9.1", | ||
| "tether": "^1.4.0" | ||
| }, | ||
| "devDependencies": { | ||
| "autoprefixer": "^6.6.1", | ||
| "babel-eslint": "^7.1.1", | ||
| "babel-plugin-transform-es2015-modules-strip": "^0.1.0", | ||
| "babel-preset-es2015": "^6.18.0", | ||
| "clean-css": "^3.4.23", | ||
| "eslint": "^3.12.2", | ||
| "grunt": "^1.0.1", | ||
| "grunt-babel": "^6.0.0", | ||
| "grunt-build-control": "^0.7.1", | ||
| "grunt-contrib-clean": "^1.0.0", | ||
| "grunt-contrib-compress": "^1.3.0", | ||
| "grunt-contrib-concat": "^1.0.1", | ||
| "grunt-contrib-connect": "^1.0.2", | ||
| "grunt-contrib-copy": "^1.0.0", | ||
| "grunt-contrib-qunit": "^1.2.0", | ||
| "grunt-contrib-watch": "^1.0.0", | ||
| "grunt-exec": "^1.0.1", | ||
| "grunt-html": "^8.1.0", | ||
| "grunt-jekyll": "^0.4.4", | ||
| "grunt-saucelabs": "^9.0.0", | ||
| "grunt-stamp": "^0.3.0", | ||
| "htmlhint": "^0.9.13", | ||
| "is-travis": "^1.0.0", | ||
| "load-grunt-tasks": "^3.5.2", | ||
| "node-sass": "^4.1.1", | ||
| "postcss-cli": "^2.6.0", | ||
| "postcss-flexbugs-fixes": "^2.1.0", | ||
| "shelljs": "^0.7.5", | ||
| "shx": "^0.2.1", | ||
| "time-grunt": "^1.4.0", | ||
| "uglify-js": "^2.7.5" | ||
| }, | ||
| "engines": { | ||
| "node": ">=4" | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "grunt", | ||
| "js/**/*.js", | ||
| "scss/**/*.scss", | ||
| "Gruntfile.js", | ||
| "LICENSE" | ||
| ], | ||
| "jspm": { | ||
| "main": "js/bootstrap", | ||
| "directories": { | ||
| "lib": "dist" | ||
| }, | ||
| "shim": { | ||
| "js/bootstrap": { | ||
| "deps": [ | ||
| "jquery" | ||
| ], | ||
| "exports": "$" | ||
| } | ||
| }, | ||
| "dependencies": { | ||
| "jquery": "3" | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "name": "bootstrap", | ||
| "description": "The most popular HTML, CSS, and JavaScript framework for developing responsive, mobile first projects on the web.", | ||
| "tags": ["bootstrap", "grid", "typography", "buttons", "ui", "responsive-web-design"] | ||
| } |
| @@ -0,0 +1,55 @@ | ||
| // | ||
| // Base styles | ||
| // | ||
|
|
||
| .alert { | ||
| padding: $alert-padding-y $alert-padding-x; | ||
| margin-bottom: $alert-margin-bottom; | ||
| border: $alert-border-width solid transparent; | ||
| @include border-radius($alert-border-radius); | ||
| } | ||
|
|
||
| // Headings for larger alerts | ||
| .alert-heading { | ||
| // Specified to prevent conflicts of changing $headings-color | ||
| color: inherit; | ||
| } | ||
|
|
||
| // Provide class for links that match alerts | ||
| .alert-link { | ||
| font-weight: $alert-link-font-weight; | ||
| } | ||
|
|
||
|
|
||
| // Dismissible alerts | ||
| // | ||
| // Expand the right padding and account for the close button's positioning. | ||
|
|
||
| .alert-dismissible { | ||
| // Adjust close link position | ||
| .close { | ||
| position: relative; | ||
| top: -$alert-padding-y; | ||
| right: -$alert-padding-x; | ||
| padding: $alert-padding-y $alert-padding-x; | ||
| color: inherit; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // Alternate styles | ||
| // | ||
| // Generate contextual modifier classes for colorizing the alert. | ||
|
|
||
| .alert-success { | ||
| @include alert-variant($alert-success-bg, $alert-success-border, $alert-success-text); | ||
| } | ||
| .alert-info { | ||
| @include alert-variant($alert-info-bg, $alert-info-border, $alert-info-text); | ||
| } | ||
| .alert-warning { | ||
| @include alert-variant($alert-warning-bg, $alert-warning-border, $alert-warning-text); | ||
| } | ||
| .alert-danger { | ||
| @include alert-variant($alert-danger-bg, $alert-danger-border, $alert-danger-text); | ||
| } |
| @@ -0,0 +1,77 @@ | ||
| // Base class | ||
| // | ||
| // Requires one of the contextual, color modifier classes for `color` and | ||
| // `background-color`. | ||
|
|
||
| .badge { | ||
| display: inline-block; | ||
| padding: $badge-padding-y $badge-padding-x; | ||
| font-size: $badge-font-size; | ||
| font-weight: $badge-font-weight; | ||
| line-height: 1; | ||
| color: $badge-color; | ||
| text-align: center; | ||
| white-space: nowrap; | ||
| vertical-align: baseline; | ||
| @include border-radius(); | ||
|
|
||
| // Empty badges collapse automatically | ||
| &:empty { | ||
| display: none; | ||
| } | ||
| } | ||
|
|
||
| // Quick fix for badges in buttons | ||
| .btn .badge { | ||
| position: relative; | ||
| top: -1px; | ||
| } | ||
|
|
||
| // scss-lint:disable QualifyingElement | ||
| // Add hover effects, but only for links | ||
| a.badge { | ||
| @include hover-focus { | ||
| color: $badge-link-hover-color; | ||
| text-decoration: none; | ||
| cursor: pointer; | ||
| } | ||
| } | ||
| // scss-lint:enable QualifyingElement | ||
|
|
||
| // Pill badges | ||
| // | ||
| // Make them extra rounded with a modifier to replace v3's badges. | ||
|
|
||
| .badge-pill { | ||
| padding-right: $badge-pill-padding-x; | ||
| padding-left: $badge-pill-padding-x; | ||
| @include border-radius($badge-pill-border-radius); | ||
| } | ||
|
|
||
| // Colors | ||
| // | ||
| // Contextual variations (linked badges get darker on :hover). | ||
|
|
||
| .badge-default { | ||
| @include badge-variant($badge-default-bg); | ||
| } | ||
|
|
||
| .badge-primary { | ||
| @include badge-variant($badge-primary-bg); | ||
| } | ||
|
|
||
| .badge-success { | ||
| @include badge-variant($badge-success-bg); | ||
| } | ||
|
|
||
| .badge-info { | ||
| @include badge-variant($badge-info-bg); | ||
| } | ||
|
|
||
| .badge-warning { | ||
| @include badge-variant($badge-warning-bg); | ||
| } | ||
|
|
||
| .badge-danger { | ||
| @include badge-variant($badge-danger-bg); | ||
| } |
| @@ -0,0 +1,38 @@ | ||
| .breadcrumb { | ||
| padding: $breadcrumb-padding-y $breadcrumb-padding-x; | ||
| margin-bottom: $spacer-y; | ||
| list-style: none; | ||
| background-color: $breadcrumb-bg; | ||
| @include border-radius($border-radius); | ||
| @include clearfix; | ||
| } | ||
|
|
||
| .breadcrumb-item { | ||
| float: left; | ||
|
|
||
| // The separator between breadcrumbs (by default, a forward-slash: "/") | ||
| + .breadcrumb-item::before { | ||
| display: inline-block; // Suppress underlining of the separator in modern browsers | ||
| padding-right: $breadcrumb-item-padding; | ||
| padding-left: $breadcrumb-item-padding; | ||
| color: $breadcrumb-divider-color; | ||
| content: "#{$breadcrumb-divider}"; | ||
| } | ||
|
|
||
| // IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built | ||
| // without `<ul>`s. The `::before` pseudo-element generates an element | ||
| // *within* the .breadcrumb-item and thereby inherits the `text-decoration`. | ||
| // | ||
| // To trick IE into suppressing the underline, we give the pseudo-element an | ||
| // underline and then immediately remove it. | ||
| + .breadcrumb-item:hover::before { | ||
| text-decoration: underline; | ||
| } | ||
| + .breadcrumb-item:hover::before { | ||
| text-decoration: none; | ||
| } | ||
|
|
||
| &.active { | ||
| color: $breadcrumb-active-color; | ||
| } | ||
| } |
| @@ -0,0 +1,202 @@ | ||
| // scss-lint:disable QualifyingElement | ||
|
|
||
| // Make the div behave like a button | ||
| .btn-group, | ||
| .btn-group-vertical { | ||
| position: relative; | ||
| display: inline-flex; | ||
| vertical-align: middle; // match .btn alignment given font-size hack above | ||
|
|
||
| > .btn { | ||
| position: relative; | ||
| flex: 0 1 auto; | ||
|
|
||
| // Bring the hover, focused, and "active" buttons to the fron to overlay | ||
| // the borders properly | ||
| @include hover { | ||
| z-index: 2; | ||
| } | ||
| &:focus, | ||
| &:active, | ||
| &.active { | ||
| z-index: 2; | ||
| } | ||
| } | ||
|
|
||
| // Prevent double borders when buttons are next to each other | ||
| .btn + .btn, | ||
| .btn + .btn-group, | ||
| .btn-group + .btn, | ||
| .btn-group + .btn-group { | ||
| margin-left: -$input-btn-border-width; | ||
| } | ||
| } | ||
|
|
||
| // Optional: Group multiple button groups together for a toolbar | ||
| .btn-toolbar { | ||
| display: flex; | ||
| justify-content: flex-start; | ||
|
|
||
| .input-group { | ||
| width: auto; | ||
| } | ||
| } | ||
|
|
||
| .btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { | ||
| border-radius: 0; | ||
| } | ||
|
|
||
| // Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match | ||
| .btn-group > .btn:first-child { | ||
| margin-left: 0; | ||
|
|
||
| &:not(:last-child):not(.dropdown-toggle) { | ||
| @include border-right-radius(0); | ||
| } | ||
| } | ||
| // Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it | ||
| .btn-group > .btn:last-child:not(:first-child), | ||
| .btn-group > .dropdown-toggle:not(:first-child) { | ||
| @include border-left-radius(0); | ||
| } | ||
|
|
||
| // Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group) | ||
| .btn-group > .btn-group { | ||
| float: left; | ||
| } | ||
| .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { | ||
| border-radius: 0; | ||
| } | ||
| .btn-group > .btn-group:first-child:not(:last-child) { | ||
| > .btn:last-child, | ||
| > .dropdown-toggle { | ||
| @include border-right-radius(0); | ||
| } | ||
| } | ||
| .btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { | ||
| @include border-left-radius(0); | ||
| } | ||
|
|
||
| // On active and open, don't show outline | ||
| .btn-group .dropdown-toggle:active, | ||
| .btn-group.open .dropdown-toggle { | ||
| outline: 0; | ||
| } | ||
|
|
||
|
|
||
| // Sizing | ||
| // | ||
| // Remix the default button sizing classes into new ones for easier manipulation. | ||
|
|
||
| .btn-group-sm > .btn { @extend .btn-sm; } | ||
| .btn-group-lg > .btn { @extend .btn-lg; } | ||
|
|
||
|
|
||
| // | ||
| // Split button dropdowns | ||
| // | ||
|
|
||
| .btn + .dropdown-toggle-split { | ||
| padding-right: $btn-padding-x * .75; | ||
| padding-left: $btn-padding-x * .75; | ||
|
|
||
| &::after { | ||
| margin-left: 0; | ||
| } | ||
| } | ||
|
|
||
| .btn-sm + .dropdown-toggle-split { | ||
| padding-right: $btn-padding-x-sm * .75; | ||
| padding-left: $btn-padding-x-sm * .75; | ||
| } | ||
|
|
||
| .btn-lg + .dropdown-toggle-split { | ||
| padding-right: $btn-padding-x-lg * .75; | ||
| padding-left: $btn-padding-x-lg * .75; | ||
| } | ||
|
|
||
|
|
||
| // The clickable button for toggling the menu | ||
| // Remove the gradient and set the same inset shadow as the :active state | ||
| .btn-group.open .dropdown-toggle { | ||
| @include box-shadow($btn-active-box-shadow); | ||
|
|
||
| // Show no shadow for `.btn-link` since it has no other button styles. | ||
| &.btn-link { | ||
| @include box-shadow(none); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Vertical button groups | ||
| // | ||
|
|
||
| .btn-group-vertical { | ||
| display: inline-flex; | ||
| flex-direction: column; | ||
| align-items: flex-start; | ||
| justify-content: center; | ||
|
|
||
| .btn, | ||
| .btn-group { | ||
| width: 100%; | ||
| } | ||
|
|
||
| > .btn + .btn, | ||
| > .btn + .btn-group, | ||
| > .btn-group + .btn, | ||
| > .btn-group + .btn-group { | ||
| margin-top: -$input-btn-border-width; | ||
| margin-left: 0; | ||
| } | ||
| } | ||
|
|
||
| .btn-group-vertical > .btn { | ||
| &:not(:first-child):not(:last-child) { | ||
| border-radius: 0; | ||
| } | ||
| &:first-child:not(:last-child) { | ||
| @include border-bottom-radius(0); | ||
| } | ||
| &:last-child:not(:first-child) { | ||
| @include border-top-radius(0); | ||
| } | ||
| } | ||
| .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { | ||
| border-radius: 0; | ||
| } | ||
| .btn-group-vertical > .btn-group:first-child:not(:last-child) { | ||
| > .btn:last-child, | ||
| > .dropdown-toggle { | ||
| @include border-bottom-radius(0); | ||
| } | ||
| } | ||
| .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { | ||
| @include border-top-radius(0); | ||
| } | ||
|
|
||
|
|
||
| // Checkbox and radio options | ||
| // | ||
| // In order to support the browser's form validation feedback, powered by the | ||
| // `required` attribute, we have to "hide" the inputs via `clip`. We cannot use | ||
| // `display: none;` or `visibility: hidden;` as that also hides the popover. | ||
| // Simply visually hiding the inputs via `opacity` would leave them clickable in | ||
| // certain cases which is prevented by using `clip` and `pointer-events`. | ||
| // This way, we ensure a DOM element is visible to position the popover from. | ||
| // | ||
| // See https://github.com/twbs/bootstrap/pull/12794 and | ||
| // https://github.com/twbs/bootstrap/pull/14559 for more information. | ||
|
|
||
| [data-toggle="buttons"] { | ||
| > .btn, | ||
| > .btn-group > .btn { | ||
| input[type="radio"], | ||
| input[type="checkbox"] { | ||
| position: absolute; | ||
| clip: rect(0,0,0,0); | ||
| pointer-events: none; | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,170 @@ | ||
| // scss-lint:disable QualifyingElement | ||
|
|
||
| // | ||
| // Base styles | ||
| // | ||
|
|
||
| .btn { | ||
| display: inline-block; | ||
| font-weight: $btn-font-weight; | ||
| line-height: $btn-line-height; | ||
| text-align: center; | ||
| white-space: nowrap; | ||
| vertical-align: middle; | ||
| user-select: none; | ||
| border: $input-btn-border-width solid transparent; | ||
| @include button-size($btn-padding-y, $btn-padding-x, $font-size-base, $btn-border-radius); | ||
| @include transition($btn-transition); | ||
|
|
||
| // Share hover and focus styles | ||
| @include hover-focus { | ||
| text-decoration: none; | ||
| } | ||
| &:focus, | ||
| &.focus { | ||
| outline: 0; | ||
| box-shadow: $btn-focus-box-shadow; | ||
| } | ||
|
|
||
| // Disabled comes first so active can properly restyle | ||
| &.disabled, | ||
| &:disabled { | ||
| cursor: $cursor-disabled; | ||
| opacity: .65; | ||
| @include box-shadow(none); | ||
| } | ||
|
|
||
| &:active, | ||
| &.active { | ||
| background-image: none; | ||
| @include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow); | ||
| } | ||
| } | ||
|
|
||
| // Future-proof disabling of clicks on `<a>` elements | ||
| a.btn.disabled, | ||
| fieldset[disabled] a.btn { | ||
| pointer-events: none; | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Alternate buttons | ||
| // | ||
|
|
||
| .btn-primary { | ||
| @include button-variant($btn-primary-color, $btn-primary-bg, $btn-primary-border); | ||
| } | ||
| .btn-secondary { | ||
| @include button-variant($btn-secondary-color, $btn-secondary-bg, $btn-secondary-border); | ||
| } | ||
| .btn-info { | ||
| @include button-variant($btn-info-color, $btn-info-bg, $btn-info-border); | ||
| } | ||
| .btn-success { | ||
| @include button-variant($btn-success-color, $btn-success-bg, $btn-success-border); | ||
| } | ||
| .btn-warning { | ||
| @include button-variant($btn-warning-color, $btn-warning-bg, $btn-warning-border); | ||
| } | ||
| .btn-danger { | ||
| @include button-variant($btn-danger-color, $btn-danger-bg, $btn-danger-border); | ||
| } | ||
|
|
||
| // Remove all backgrounds | ||
| .btn-outline-primary { | ||
| @include button-outline-variant($btn-primary-bg); | ||
| } | ||
| .btn-outline-secondary { | ||
| @include button-outline-variant($btn-secondary-border); | ||
| } | ||
| .btn-outline-info { | ||
| @include button-outline-variant($btn-info-bg); | ||
| } | ||
| .btn-outline-success { | ||
| @include button-outline-variant($btn-success-bg); | ||
| } | ||
| .btn-outline-warning { | ||
| @include button-outline-variant($btn-warning-bg); | ||
| } | ||
| .btn-outline-danger { | ||
| @include button-outline-variant($btn-danger-bg); | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Link buttons | ||
| // | ||
|
|
||
| // Make a button look and behave like a link | ||
| .btn-link { | ||
| font-weight: $font-weight-normal; | ||
| color: $link-color; | ||
| border-radius: 0; | ||
|
|
||
| &, | ||
| &:active, | ||
| &.active, | ||
| &:disabled { | ||
| background-color: transparent; | ||
| @include box-shadow(none); | ||
| } | ||
| &, | ||
| &:focus, | ||
| &:active { | ||
| border-color: transparent; | ||
| } | ||
| @include hover { | ||
| border-color: transparent; | ||
| } | ||
| @include hover-focus { | ||
| color: $link-hover-color; | ||
| text-decoration: $link-hover-decoration; | ||
| background-color: transparent; | ||
| } | ||
| &:disabled { | ||
| color: $btn-link-disabled-color; | ||
|
|
||
| @include hover-focus { | ||
| text-decoration: none; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Button Sizes | ||
| // | ||
|
|
||
| .btn-lg { | ||
| // line-height: ensure even-numbered height of button next to large input | ||
| @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $font-size-lg, $btn-border-radius-lg); | ||
| } | ||
| .btn-sm { | ||
| // line-height: ensure proper height of button next to small input | ||
| @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $font-size-sm, $btn-border-radius-sm); | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Block button | ||
| // | ||
|
|
||
| .btn-block { | ||
| display: block; | ||
| width: 100%; | ||
| } | ||
|
|
||
| // Vertically space out multiple block buttons | ||
| .btn-block + .btn-block { | ||
| margin-top: $btn-block-spacing-y; | ||
| } | ||
|
|
||
| // Specificity overrides | ||
| input[type="submit"], | ||
| input[type="reset"], | ||
| input[type="button"] { | ||
| &.btn-block { | ||
| width: 100%; | ||
| } | ||
| } |
| @@ -0,0 +1,276 @@ | ||
| // | ||
| // Base styles | ||
| // | ||
|
|
||
| .card { | ||
| position: relative; | ||
| display: flex; | ||
| flex-direction: column; | ||
| background-color: $card-bg; | ||
| border: $card-border-width solid $card-border-color; | ||
| @include border-radius($card-border-radius); | ||
| } | ||
|
|
||
| .card-block { | ||
| // Enable `flex-grow: 1` for decks and groups so that card blocks take up | ||
| // as much space as possible, ensuring footers are aligned to the bottom. | ||
| flex: 1 1 auto; | ||
| padding: $card-spacer-x; | ||
| } | ||
|
|
||
| .card-title { | ||
| margin-bottom: $card-spacer-y; | ||
| } | ||
|
|
||
| .card-subtitle { | ||
| margin-top: -($card-spacer-y / 2); | ||
| margin-bottom: 0; | ||
| } | ||
|
|
||
| .card-text:last-child { | ||
| margin-bottom: 0; | ||
| } | ||
|
|
||
| .card-link { | ||
| @include hover { | ||
| text-decoration: none; | ||
| } | ||
|
|
||
| + .card-link { | ||
| margin-left: $card-spacer-x; | ||
| } | ||
| } | ||
|
|
||
| .card { | ||
| > .list-group:first-child { | ||
| .list-group-item:first-child { | ||
| @include border-top-radius($card-border-radius); | ||
| } | ||
| } | ||
|
|
||
| > .list-group:last-child { | ||
| .list-group-item:last-child { | ||
| @include border-bottom-radius($card-border-radius); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Optional textual caps | ||
| // | ||
|
|
||
| .card-header { | ||
| padding: $card-spacer-y $card-spacer-x; | ||
| margin-bottom: 0; // Removes the default margin-bottom of <hN> | ||
| background-color: $card-cap-bg; | ||
| border-bottom: $card-border-width solid $card-border-color; | ||
|
|
||
| &:first-child { | ||
| @include border-radius($card-border-radius-inner $card-border-radius-inner 0 0); | ||
| } | ||
| } | ||
|
|
||
| .card-footer { | ||
| padding: $card-spacer-y $card-spacer-x; | ||
| background-color: $card-cap-bg; | ||
| border-top: $card-border-width solid $card-border-color; | ||
|
|
||
| &:last-child { | ||
| @include border-radius(0 0 $card-border-radius-inner $card-border-radius-inner); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Header navs | ||
| // | ||
|
|
||
| .card-header-tabs { | ||
| margin-right: -($card-spacer-x / 2); | ||
| margin-bottom: -$card-spacer-y; | ||
| margin-left: -($card-spacer-x / 2); | ||
| border-bottom: 0; | ||
| } | ||
|
|
||
| .card-header-pills { | ||
| margin-right: -($card-spacer-x / 2); | ||
| margin-left: -($card-spacer-x / 2); | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Background variations | ||
| // | ||
|
|
||
| .card-primary { | ||
| @include card-variant($brand-primary, $brand-primary); | ||
| } | ||
| .card-success { | ||
| @include card-variant($brand-success, $brand-success); | ||
| } | ||
| .card-info { | ||
| @include card-variant($brand-info, $brand-info); | ||
| } | ||
| .card-warning { | ||
| @include card-variant($brand-warning, $brand-warning); | ||
| } | ||
| .card-danger { | ||
| @include card-variant($brand-danger, $brand-danger); | ||
| } | ||
|
|
||
| // Remove all backgrounds | ||
| .card-outline-primary { | ||
| @include card-outline-variant($btn-primary-bg); | ||
| } | ||
| .card-outline-secondary { | ||
| @include card-outline-variant($btn-secondary-border); | ||
| } | ||
| .card-outline-info { | ||
| @include card-outline-variant($btn-info-bg); | ||
| } | ||
| .card-outline-success { | ||
| @include card-outline-variant($btn-success-bg); | ||
| } | ||
| .card-outline-warning { | ||
| @include card-outline-variant($btn-warning-bg); | ||
| } | ||
| .card-outline-danger { | ||
| @include card-outline-variant($btn-danger-bg); | ||
| } | ||
|
|
||
| // | ||
| // Inverse text within a card for use with dark backgrounds | ||
| // | ||
|
|
||
| .card-inverse { | ||
| @include card-inverse; | ||
| } | ||
|
|
||
| // | ||
| // Blockquote | ||
| // | ||
|
|
||
| .card-blockquote { | ||
| padding: 0; | ||
| margin-bottom: 0; | ||
| border-left: 0; | ||
| } | ||
|
|
||
| // Card image | ||
| .card-img { | ||
| // margin: -1.325rem; | ||
| @include border-radius($card-border-radius-inner); | ||
| } | ||
| .card-img-overlay { | ||
| position: absolute; | ||
| top: 0; | ||
| right: 0; | ||
| bottom: 0; | ||
| left: 0; | ||
| padding: $card-img-overlay-padding; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| // Card image caps | ||
| .card-img-top { | ||
| @include border-top-radius($card-border-radius-inner); | ||
| } | ||
| .card-img-bottom { | ||
| @include border-bottom-radius($card-border-radius-inner); | ||
| } | ||
|
|
||
|
|
||
| // Card deck | ||
|
|
||
| @include media-breakpoint-up(sm) { | ||
| .card-deck { | ||
| display: flex; | ||
| flex-flow: row wrap; | ||
|
|
||
| .card { | ||
| display: flex; | ||
| flex: 1 0 0; | ||
| flex-direction: column; | ||
|
|
||
| // Selectively apply horizontal margins to cards to avoid doing the | ||
| // negative margin dance like our grid. This differs from the grid | ||
| // due to the use of margins as gutters instead of padding. | ||
| &:not(:first-child) { margin-left: $card-deck-margin; } | ||
| &:not(:last-child) { margin-right: $card-deck-margin; } | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Card groups | ||
| // | ||
|
|
||
| @include media-breakpoint-up(sm) { | ||
| .card-group { | ||
| display: flex; | ||
| flex-flow: row wrap; | ||
|
|
||
| .card { | ||
| flex: 1 0 0; | ||
|
|
||
| + .card { | ||
| margin-left: 0; | ||
| border-left: 0; | ||
| } | ||
|
|
||
| // Handle rounded corners | ||
| @if $enable-rounded { | ||
| &:first-child { | ||
| @include border-right-radius(0); | ||
|
|
||
| .card-img-top { | ||
| border-top-right-radius: 0; | ||
| } | ||
| .card-img-bottom { | ||
| border-bottom-right-radius: 0; | ||
| } | ||
| } | ||
| &:last-child { | ||
| @include border-left-radius(0); | ||
|
|
||
| .card-img-top { | ||
| border-top-left-radius: 0; | ||
| } | ||
| .card-img-bottom { | ||
| border-bottom-left-radius: 0; | ||
| } | ||
| } | ||
|
|
||
| &:not(:first-child):not(:last-child) { | ||
| border-radius: 0; | ||
|
|
||
| .card-img-top, | ||
| .card-img-bottom { | ||
| border-radius: 0; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // | ||
| // Columns | ||
| // | ||
|
|
||
| @include media-breakpoint-up(sm) { | ||
| .card-columns { | ||
| column-count: $card-columns-count; | ||
| column-gap: $card-columns-gap; | ||
|
|
||
| .card { | ||
| display: inline-block; // Don't let them vertically span multiple columns | ||
| width: 100%; // Don't let their width change | ||
| margin-bottom: $card-columns-margin; | ||
| } | ||
| } | ||
| } |