Skip to content

Commit

Permalink
feat #113 (quantity selector): create new component
Browse files Browse the repository at this point in the history
  • Loading branch information
MewenLeHo authored and julien-deramond committed Dec 27, 2021
1 parent aa669a3 commit 06e01aa
Show file tree
Hide file tree
Showing 14 changed files with 369 additions and 0 deletions.
1 change: 1 addition & 0 deletions js/index.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { default as Dropdown } from './src/dropdown'
export { default as Modal } from './src/modal'
export { default as Offcanvas } from './src/offcanvas'
export { default as Popover } from './src/popover'
export { default as QuantitySelector } from './src/quantity-selector' // Boosted mod
export { default as ScrollSpy } from './src/scrollspy'
export { default as Tab } from './src/tab'
export { default as Toast } from './src/toast'
Expand Down
2 changes: 2 additions & 0 deletions js/index.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Dropdown from './src/dropdown'
import Modal from './src/modal'
import Offcanvas from './src/offcanvas'
import Popover from './src/popover'
import QuantitySelector from './src/quantity-selector' // Boosted mod
import ScrollSpy from './src/scrollspy'
import Tab from './src/tab'
import Toast from './src/toast'
Expand All @@ -28,6 +29,7 @@ export default {
Modal,
Offcanvas,
Popover,
QuantitySelector, // Boosted mod
ScrollSpy,
Tab,
Toast,
Expand Down
79 changes: 79 additions & 0 deletions js/src/quantity-selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* --------------------------------------------------------------------------
* Boosted (v5.1.3): quantity-selector.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'

/**
* Constants
*/

const NAME = 'quantityselector'
const DATA_KEY = 'bs.quantityselector'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
const SELECTOR_STEP_UP_BUTTON = '[data-bs-step="up"]'
const SELECTOR_STEP_DOWN_BUTTON = '[data-bs-step="down"]'
const SELECTOR_COUNTER_INPUT = '[data-bs-step="counter"]'
const SELECTOR_INPUT_GROUP = '.input-group'

/**
* Class definition
*/

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

// Public
static StepUp(event) {
event.preventDefault()
const PARENT = event.target.closest(SELECTOR_INPUT_GROUP)
const COUNTER_INPUT = PARENT.querySelector(SELECTOR_COUNTER_INPUT)

const MAX = COUNTER_INPUT.getAttribute('max')
const STEP = Number(COUNTER_INPUT.getAttribute('step'))
const ROUND = Number(COUNTER_INPUT.getAttribute('data-bs-round'))

if (Number(COUNTER_INPUT.value) < MAX) {
COUNTER_INPUT.value = (Number(COUNTER_INPUT.value) + STEP).toFixed(ROUND).toString()
}
}

static StepDown(event) {
event.preventDefault()
const PARENT = event.target.closest(SELECTOR_INPUT_GROUP)
const COUNTER_INPUT = PARENT.querySelector(SELECTOR_COUNTER_INPUT)

const MIN = COUNTER_INPUT.getAttribute('min')
const STEP = Number(COUNTER_INPUT.getAttribute('step'))
const ROUND = Number(COUNTER_INPUT.getAttribute('data-bs-round'))

if (Number(COUNTER_INPUT.value) > MIN) {
COUNTER_INPUT.value = (Number(COUNTER_INPUT.value) - STEP).toFixed(ROUND).toString()
}
}
}

/**
* Data API implementation
*/

EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_STEP_UP_BUTTON, QuantitySelector.StepUp)
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_STEP_DOWN_BUTTON, QuantitySelector.StepDown)

/**
* jQuery
*/

defineJQueryPlugin(QuantitySelector)

export default QuantitySelector
86 changes: 86 additions & 0 deletions js/tests/unit/quantity-selector.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import QuantitySelector from '../../src/quantity-selector'
import { clearFixture, getFixture } from '../helpers/fixture'

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

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

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

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

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

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

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

it('should take care of element either passed as a CSS selector or DOM element - Step Up button', () => {
fixtureEl.innerHTML = '<button data-bs-step="up"></button>'
const buttonEl = fixtureEl.querySelector('[data-bs-step="up"]')
const buttonBySelector = new QuantitySelector('[data-bs-step="up"]')
const buttonByElement = new QuantitySelector(buttonEl)

expect(buttonBySelector._element).toEqual(buttonEl)
expect(buttonByElement._element).toEqual(buttonEl)
})

it('should take care of element either passed as a CSS selector or DOM element - Step Down button', () => {
fixtureEl.innerHTML = '<button data-bs-step="down"></button>'
const buttonEl = fixtureEl.querySelector('[data-bs-step="down"]')
const buttonBySelector = new QuantitySelector('[data-bs-step="down"]')
const buttonByElement = new QuantitySelector(buttonEl)

expect(buttonBySelector._element).toEqual(buttonEl)
expect(buttonByElement._element).toEqual(buttonEl)
})

it('should increment by one step on click on StepUp button', () => {
fixtureEl.innerHTML = '<button data-bs-step="up"></button>'
const buttonEl = fixtureEl.querySelector('[data-bs-step="up"]')
fixtureEl.innerHTML = '<input data-bs-step="counter"></input>'
const inputEl = fixtureEl.querySelector('[data-bs-step="counter"]')
const counterStep = inputEl.getAttribute('step')
const counterMax = inputEl.getAttribute('max')
const counterValue = inputEl.value

buttonEl.click()

expect(inputEl.value === counterValue + counterStep || inputEl.value === counterMax)
})

it('should decrement by one step on click on StepDown button', () => {
fixtureEl.innerHTML = '<button data-bs-step="down"></button>'
const buttonEl = fixtureEl.querySelector('[data-bs-step="down"]')
fixtureEl.innerHTML = '<input data-bs-step="counter"></input>'
const inputEl = fixtureEl.querySelector('[data-bs-step="counter"]')
const counterStep = inputEl.getAttribute('step')
const counterMin = inputEl.getAttribute('min')
const counterValue = inputEl.value

buttonEl.click()

expect(inputEl.value === counterValue - counterStep || inputEl.value === counterMin)
})
})
1 change: 1 addition & 0 deletions scss/_forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
// Boosted mod: no floating-labels
@import "forms/input-group";
@import "forms/validation";
@import "forms/quantity-selector"; // Boosted mod
24 changes: 24 additions & 0 deletions scss/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ $success-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/20
$info-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 125 125'><path fill='#{$info}' d='M62.5 0a62.5 62.5 0 1 0 0 125 62.5 62.5 0 0 0 0-125zm0 14.7a11 11 0 1 1 0 22 11 11 0 0 1 0-22zM47.8 44.1h25.7v46.2c0 4.7 1.3 6.5 1.8 7.2.8 1 2.3 1.5 4.8 1.6h.8v3.8H47.8v-3.7h.8c2.3-.1 4-.8 5-2 .4-.4 1-2 1-7V57c0-4.8-.6-6.6-1.2-7.3-.8-1-2.4-1.5-4.9-1.6h-.7V44z'/></svg>") !default;
$warning-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path fill='#{$warning}' d='M15 0a15 15 0 1 0 0 30 15 15 0 0 0 0-30zm.15 5.39h.01c1.12 0 2 .95 1.92 2.06l-.63 10.43c0 .7-.58.97-1.29.97-.72 0-1.28-.27-1.28-.97l-.63-10.46c-.06-1.09.8-2.01 1.9-2.03zm-.3 15.33c.11 0 .21 0 .31.02 2.19.35 2.19 3.5 0 3.84-2.77.44-3.1-3.86-.3-3.86z'/></svg>") !default;
$danger-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 140 125'><path fill='#{$danger}' d='M70.3 0c-5.8 0-10.8 3.1-13.5 7.8L2.3 101.3l-.2.2A15.6 15.6 0 0 0 15.6 125H125a15.6 15.6 0 0 0 13.5-23.5L83.8 7.8A15.6 15.6 0 0 0 70.3 0zm19.2 50a6.4 6.4 0 0 1 4.4 1.9 6.4 6.4 0 0 1 0 9L79.4 75.6l15 15a6.4 6.4 0 0 1 0 9.2 6.4 6.4 0 0 1-4.5 1.9 6.4 6.4 0 0 1-4.6-2l-15-15-15 15a6.4 6.4 0 0 1-4.6 2 6.4 6.4 0 0 1-4.6-2 6.4 6.4 0 0 1 0-9l15-15L46.8 61a6.4 6.4 0 1 1 9-9.1l14.6 14.5L84.8 52a6.4 6.4 0 0 1 4.7-1.9z'/></svg>") !default;
$add-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><path fill='currentColor' d='M6 0h2v6h6v2H8v6H6V8H0V6h6z'/></svg>") !default;
$remove-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 2'><path fill='currentColor' d='M0 0v2h14V0H0Z'/></svg>") !default;

//// SVG used several times
$svg-as-custom-props: (
"chevron": $chevron-icon,
Expand All @@ -368,6 +371,7 @@ $svg-as-custom-props: (
"success": $success-icon,
"error": $danger-icon
) !default;

//// Filters
// see https://codepen.io/sosuke/pen/Pjoqqp
$invert-filter: invert(1) !default;
Expand Down Expand Up @@ -971,6 +975,8 @@ $input-line-height-lg: $h5-line-height !default; // Boosted mod
$input-transition: border-color $transition-duration $transition-timing, $transition-focus !default;

$form-color-width: 3rem !default;
// End mod

// scss-docs-end form-input-variables

// scss-docs-start form-check-variables
Expand Down Expand Up @@ -1838,4 +1844,22 @@ $step-link-marker-lg: counter($stepped-process-counter) ".\A0" !default;
$step-item-arrow-width: 1rem !default;
$step-item-arrow-shape: polygon(0% 0%, subtract(100%, $border-width) 50%, 0% 100%) #{"/* rtl:"} polygon(100% 0%, $border-width 50%, 100% 100%) #{"*/"} !default; // Used in clip-path
// scss-docs-end stepped-process

//// Quantity selector
// scss-docs-start quantity-selector
$quantity-selector-icon-add: $add-icon !default;
$quantity-selector-icon-remove: $remove-icon !default;

$quantity-selector-sm-width: 5.625rem !default;
$quantity-selector-input-width: 2.75rem !default;
$quantity-selector-input-sm-width: 2.125rem !default;

$quantity-selector-icon-width: .875rem !default;
$quantity-selector-icon-remove-height: .125rem !default;
$quantity-selector-icon-add-height: .875rem !default;

$quantity-selector-icon-sm-width: .625rem !default;
$quantity-selector-icon-sm-remove-height: .125rem !default;
$quantity-selector-icon-sm-add-height: .625rem !default;
// scss-docs-end quantity-selector
// End mod
64 changes: 64 additions & 0 deletions scss/forms/_quantity-selector.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.quantity-selector {
width: 7.5em;
$validation-messages: "";

@each $state in map-keys($form-validation-states) {
$validation-messages: $validation-messages + ":not(." + unquote($state) + "-tooltip)" + ":not(." + unquote($state) + "-feedback)";
}

> :not(:first-child):not(.dropdown-menu)#{$validation-messages} {
margin-left: 0;
}

.form-control {
max-width: $quantity-selector-input-width;
padding: 0;
font-size: $font-size-sm;
text-align: center;
transition: none; // stylelint-disable-line property-disallowed-list
appearance: textfield;

&:not(:focus) {
border-right: none;
border-left: none;
}

&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
margin: 0;
appearance: none;
}
}

button {
border: $border-width solid $gray-500;

&:first-of-type {
@include button-icon($quantity-selector-icon-remove, $size: $quantity-selector-icon-width $quantity-selector-icon-remove-height, $pseudo: "after");
order: -1;
border-right: none;

&.btn-sm { // stylelint-disable-line selector-no-qualifying-type
@include button-icon($quantity-selector-icon-remove, $width: 1rem, $height: 1rem, $size: $quantity-selector-icon-sm-width $quantity-selector-icon-sm-remove-height, $pseudo: "after");
}
}

&:last-of-type {
@include button-icon($quantity-selector-icon-add, $size: $quantity-selector-icon-width $quantity-selector-icon-add-height, $pseudo: "after");
border-left: none;

&.btn-sm { // stylelint-disable-line selector-no-qualifying-type
@include button-icon($quantity-selector-icon-add, $width: 1rem, $height: 1rem, $size: $quantity-selector-icon-sm-width $quantity-selector-icon-sm-add-height, $pseudo: "after");
}
}
}
}

.quantity-selector-sm {
width: $quantity-selector-sm-width;

.form-control {
max-width: $quantity-selector-input-sm-width;
font-size: $font-size-base;
}
}
14 changes: 14 additions & 0 deletions scss/mixins/_forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@
}
}

// Boosted mod: Remove border on input for form element QuantitySelector
.quantity-selector {
.form-control {
@include form-validation-state-selector($state) {
border-right: none;
border-left: none;
& ~ button {
border-color: $color;
}
}
}
}
// End mod

// Boosted mod: no icon in background for textarea

.form-select {
Expand Down
1 change: 1 addition & 0 deletions site/content/docs/5.1/about/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Boosted ships with custom accessible components to suit specific needs:

- [Back to top]({{< docsref "/components/back-to-top" >}})
- [Orange Navbars]({{< docsref "/components/orange-navbar" >}})
- [Quantity selector]({{< docsref "/forms/quantity-selector" >}})
- [Stepped process]({{< docsref "/components/stepped-process" >}})


Expand Down
1 change: 1 addition & 0 deletions site/content/docs/5.1/customize/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ Several Boosted components include embedded SVGs in our CSS to style components
- [Carousel controls]({{< docsref "/components/carousel#with-controls" >}})
- [Navbar toggle buttons]({{< docsref "/components/navbar#responsive-behaviors" >}})
- [Pagination]({{< docsref "/components/pagination" >}}) <!-- Boosted mod -->
- [Quantity selector buttons]({{< docsref "/forms/quantity-selector" >}}) <!-- Boosted mod -->

Based on [community conversation](https://github.com/twbs/bootstrap/issues/25394), some options for addressing this in your own codebase include replacing the URLs with locally hosted assets, removing the images and using inline images (not possible in all components), and modifying your CSP. Our recommendation is to carefully review your own security policies and decide on the best path forward, if necessary.
Loading

0 comments on commit 06e01aa

Please sign in to comment.