diff --git a/toolkits/nature/packages/nature-sort-by/HISTORY.md b/toolkits/nature/packages/nature-sort-by/HISTORY.md
index 3969878f0..f2e57ebf9 100644
--- a/toolkits/nature/packages/nature-sort-by/HISTORY.md
+++ b/toolkits/nature/packages/nature-sort-by/HISTORY.md
@@ -1,5 +1,8 @@
# History
+## 3.0.0 (2022-02-14)
+ * BREAKING: Performs a complete rewrite of the component. Component no longer is a dropdown, now simpler implementation with radio buttons in a form.
+
## 2.1.0 (2022-02-02)
* FEATURE: add view template and demo
@@ -8,6 +11,5 @@
## 1.1.0 (2021-06-30)
* Ensures radio is checked before page reloads
-
## 1.0.0 (2021-05-19)
* Initial commit
diff --git a/toolkits/nature/packages/nature-sort-by/README.md b/toolkits/nature/packages/nature-sort-by/README.md
index f12a9192b..675f9382e 100644
--- a/toolkits/nature/packages/nature-sort-by/README.md
+++ b/toolkits/nature/packages/nature-sort-by/README.md
@@ -2,7 +2,7 @@
[![NPM version][badge-npm]][info-npm]
-A sort by dropdown for use on Nature product pages that contain a sortable list of content such as search results pages.
+The Nature Sort by component lets users change the order of a list of content items. For example, to sort search results by date or relevance.
## Installation
@@ -25,34 +25,17 @@ sortBy();
```scss
@import '@springernature/nature-sort-by/scss/10-settings/sort-by';
@import '@springernature/nature-sort-by/scss/50-components/sort-by';
-
-@import '@springernature/brand-context/default/scss/60-utilities/flex.scss';
-@import '@springernature/brand-context/default/scss/60-utilities/hiding.scss';
-@import '@springernature/brand-context/default/scss/60-utilities/spacing.scss';
-
```
-> **NOTE** The component require the use of the utility classes shown above
+Insert the component's HTML inside an HTML form in your application. See an example [template](#template) below.
## Configuration
-To configure the component the following html attributes will need to be set:
-
-| Name | Description |
-|------------------------|---------------------------------------------------------------------------------------------|
-| data-sort-by-trigger | This should be set on the html element that is clicked in order to open the dropdown menu |
-| data-sort-by-target | This should be set on the containing html element of the sort by dropdown menu |
-| data-sort-by-radio | This should be set on each list item within the sort by dropdown menu |
-| {
+ beforeEach(() => {
+ document.body.innerHTML = `
+
+ `;
+ });
+
+ test('Should submit form if radio input checked', () => {
+ const form = document.querySelector('form');
+ const spy = jest.fn();
+ form.addEventListener('submit', event => {
+ event.preventDefault();
+ spy();
+ });
+ sortBy();
+ const input = document.querySelector('input');
+ input.dispatchEvent(new Event('change'));
+ expect(spy).toHaveBeenCalled();
+ });
+});
diff --git a/toolkits/nature/packages/nature-sort-by/__tests__/sort-by.spec.js b/toolkits/nature/packages/nature-sort-by/__tests__/sort-by.spec.js
deleted file mode 100644
index 164653245..000000000
--- a/toolkits/nature/packages/nature-sort-by/__tests__/sort-by.spec.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import {sortBy} from '../js/sort-by';
-
-describe('sortBy', () => {
- const element = {};
-
- beforeEach(() => {
- document.body.innerHTML = `
-
-
-
- `;
-
- element.target1 = document.querySelector('#sort-by');
-
- element.button1 = document.querySelector('[data-sort-by-trigger]');
- });
-
- afterEach(() => {
- document.body.innerHTML = '';
- });
-
- test('should set role for button', () => {
- // Given
- sortBy();
- // Then
- expect(element.button1.getAttribute('role')).toBe('button');
- });
-
- test('should append target after trigger', () => {
- // Given
- sortBy();
- // When
- element.targetAfterButton1 = element.button1.nextSibling
- // Then
- expect(element.targetAfterButton1).toBe(element.target1);
- });
-});
diff --git a/toolkits/nature/packages/nature-sort-by/demo/context.json b/toolkits/nature/packages/nature-sort-by/demo/context.json
index 9b987fa27..eca65ec9c 100644
--- a/toolkits/nature/packages/nature-sort-by/demo/context.json
+++ b/toolkits/nature/packages/nature-sort-by/demo/context.json
@@ -3,15 +3,20 @@
{
"value": "relevance",
"label": "Relevance",
- "checked": true
+ "name": "order",
+ "checked": false
},
{
"value": "desc",
- "label": "Most recent"
- },
+ "label": "Date published (new to old)",
+ "name": "order",
+ "checked": true
+ },
{
"value": "asc",
- "label": "Oldest first"
+ "label": "Date published (old to new)",
+ "name": "order",
+ "checked": false
}
],
"dynamicPartials": {
diff --git a/toolkits/nature/packages/nature-sort-by/demo/dist/index.html b/toolkits/nature/packages/nature-sort-by/demo/dist/index.html
index 6149b74b2..49b10c565 100644
--- a/toolkits/nature/packages/nature-sort-by/demo/dist/index.html
+++ b/toolkits/nature/packages/nature-sort-by/demo/dist/index.html
@@ -3,6 +3,7 @@
(function(e){var t=e.documentElement,n=e.implementation;t.className='js';})(document)
+
@springernature/nature-sort-by
-
- function _defineProperties(target, props) {
- for (var i = 0; i < props.length; i++) {
- var descriptor = props[i];
- descriptor.enumerable = descriptor.enumerable || false;
- descriptor.configurable = true;
- if ("value" in descriptor) descriptor.writable = true;
- Object.defineProperty(target, descriptor.key, descriptor);
- }
- }
-
- function _createClass(Constructor, protoProps, staticProps) {
- if (protoProps) _defineProperties(Constructor.prototype, protoProps);
- if (staticProps) _defineProperties(Constructor, staticProps);
- Object.defineProperty(Constructor, "prototype", {
- writable: false
- });
- return Constructor;
- }
-
- var makeArray = function makeArray(iterable) {
- var list = [];
-
- if (!iterable) {
- return list;
- }
-
- Array.prototype.forEach.call(iterable, function (item) {
- list.push(item);
- });
- return list;
- };
-
- /**
- * create-event.js
- * Wrapper for customEvent
- * Generate namespaced events
- */
- /**
- * Polyfill customEvent
- * @function customEvent
- * @param {String} eventName namespaced name of the event
- * @param {Object=} _parameters customEvent options
- * @return {Event}
- */
-
- var customEvent = function customEvent(eventName, _parameters) {
- _parameters = _parameters || {
- bubbles: false,
- cancelable: false,
- detail: null
- };
- var event = document.createEvent('CustomEvent');
- event.initCustomEvent(eventName, _parameters.bubbles, _parameters.cancelable, _parameters.detail);
- return event;
- };
- /**
- * Create a custom event
- * @function createEvent
- * @param {String} eventName name of the event
- * @param {String} namespace namespace e.g. component name
- * @param {Object=} _parameters customEvent options
- * @return {Event}
- */
-
-
- var createEvent = function createEvent(eventName, namespace, _parameters) {
- var event;
-
- if (namespace === undefined) {
- throw new Error('Missing namespace in `createEvent` function');
- }
-
- if (typeof window.CustomEvent === 'function') {
- event = new CustomEvent("".concat(namespace, ":").concat(eventName), _parameters);
- } else {
- event = customEvent("".concat(namespace, ":").concat(eventName), _parameters);
- }
-
- return event;
- };
-
- /**
- * Local Constants
- */
-
- var defaultOptions = {
- TARGET_HIDE_CLASS: 'u-js-hide',
- TRIGGER_OPEN_CLASS: 'is-open',
- TRIGGER_OPEN_LABEL: undefined,
- CLOSE_ON_FOCUS_OUT: true,
- AUTOFOCUS: null,
- OPEN_EVENT: false,
- DEFAULT_OPEN: false
- };
-
- var Expander = /*#__PURE__*/function () {
- function Expander(trigger, target) {
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
-
- _classCallCheck(this, Expander);
-
- this._options = Object.assign({}, defaultOptions, options);
- this._autoFocusElement = trigger;
- this._triggerEl = trigger;
- this._targetEl = target;
- this._originalTriggerText = trigger.textContent;
- this._isOpen = this._options.DEFAULT_OPEN;
- this._handleButtonClick = this._handleButtonClick.bind(this);
- this._handleButtonKeydown = this._handleButtonKeydown.bind(this);
- this._handleDocumentClick = this._handleDocumentClick.bind(this);
- this._handleDocumentKeydown = this._handleDocumentKeydown.bind(this);
- }
- /**
- * Event Handlers
- */
-
-
- _createClass(Expander, [{
- key: "_handleButtonClick",
- value: function _handleButtonClick(event) {
- event.preventDefault();
-
- if (this._isOpen) {
- this.close();
- } else {
- this.open();
- }
- }
- }, {
- key: "_handleButtonKeydown",
- value: function _handleButtonKeydown(event) {
- if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
- event.preventDefault();
-
- if (this._isOpen) {
- this.close();
- } else {
- this.open();
- }
- }
- }
- }, {
- key: "_handleDocumentKeydown",
- value: function _handleDocumentKeydown(event) {
- var _this = this;
-
- if (event.key === 'Escape') {
- this.close();
-
- this._triggerEl.focus();
- }
-
- if (this._options.CLOSE_ON_FOCUS_OUT) {
- if (event.key === 'Tab' && event.shiftKey === true) {
- if (event.target === this._targetTabbableItems[0] || event.target === this._triggerEl || event.target === this._targetEl) {
- event.preventDefault();
- window.requestAnimationFrame(function () {
- _this.close();
-
- _this._triggerEl.focus();
- });
- }
- }
-
- if (event.key === 'Tab' && event.shiftKey === false) {
- if (event.target === this._targetTabbableItems[this._targetTabbableItems.length - 1]) {
- event.preventDefault();
- window.requestAnimationFrame(function () {
- _this.close();
-
- _this._triggerEl.focus();
- });
- }
- }
- }
- }
- }, {
- key: "_handleDocumentClick",
- value: function _handleDocumentClick(event) {
- var target = event.target;
-
- if (target === this._targetEl || target === this._triggerEl || this._targetEl.contains(target) || this._triggerEl.contains(target)) {
- return;
- }
-
- this.close();
- }
- /**
- * Temporary Event Listeners
- */
-
- }, {
- key: "_setupTemporaryEventListeners",
- value: function _setupTemporaryEventListeners() {
- document.addEventListener('keydown', this._handleDocumentKeydown);
-
- if (this._options.CLOSE_ON_FOCUS_OUT) {
- document.addEventListener('click', this._handleDocumentClick);
- }
- }
- }, {
- key: "_removeTemporaryEventListeners",
- value: function _removeTemporaryEventListeners() {
- document.removeEventListener('keydown', this._handleDocumentKeydown);
-
- if (this._options.CLOSE_ON_FOCUS_OUT) {
- document.removeEventListener('click', this._handleDocumentClick);
- }
- }
- /**
- * Attributes
- */
-
- }, {
- key: "_updateAttributes",
- value: function _updateAttributes() {
- // eslint-disable-next-line unicorn/consistent-function-scoping
- this._triggerEl.setAttribute('aria-expanded', this._isOpen.toString());
-
- if (this._isOpen) {
- this._targetEl.removeAttribute('hidden');
- } else {
- this._targetEl.setAttribute('hidden', '');
- }
- }
- /**
- * Class attributes
- */
-
- }, {
- key: "_updateClassAttributes",
- value: function _updateClassAttributes() {
- if (this._isOpen) {
- this._triggerEl.classList.add(this._options.TRIGGER_OPEN_CLASS);
-
- this._targetEl.classList.remove(this._options.TARGET_HIDE_CLASS);
- } else {
- this._triggerEl.classList.remove(this._options.TRIGGER_OPEN_CLASS);
-
- this._targetEl.classList.add(this._options.TARGET_HIDE_CLASS);
- }
- }
- /**
- * Trigger Label
- */
-
- }, {
- key: "_updateTriggerLabel",
- value: function _updateTriggerLabel() {
- if (this._options.TRIGGER_OPEN_LABEL) {
- this._triggerEl.textContent = this._isOpen ? this._options.TRIGGER_OPEN_LABEL : this._originalTriggerText;
- }
- }
- /**
- * Tabbable Items
- */
-
- }, {
- key: "_updateTabbableItems",
- value: function _updateTabbableItems() {
- this._targetTabbableItems = makeArray(this._targetEl.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')).filter(function (element) {
- return window.getComputedStyle(element).getPropertyValue('visibility') !== 'hidden';
- });
- }
- /**
- * AutoFocus
- */
-
- }, {
- key: "_handleAutoFocus",
- value: function _handleAutoFocus() {
- if (this._options.AUTOFOCUS === 'target') {
- this._autoFocusElement = this._targetEl;
-
- this._targetEl.setAttribute('tabindex', '-1');
- }
-
- if (this._options.AUTOFOCUS === 'firstTabbable') {
- this._autoFocusElement = this._targetTabbableItems.length > 0 && this._targetTabbableItems[0];
-
- if (this._autoFocusElement.setSelectionRange) {
- this._autoFocusElement.setSelectionRange(0, this._autoFocusElement.value.length);
- }
- }
-
- this._autoFocusElement.focus();
- }
- /**
- * @public
- */
-
- /**
- * Toggling
- */
-
- }, {
- key: "open",
- value: function open() {
- if (this._isOpen) {
- return;
- }
-
- this._isOpen = true;
-
- this._updateTriggerLabel();
-
- this._updateAttributes();
-
- this._updateClassAttributes();
-
- this._updateTabbableItems();
-
- this._setupTemporaryEventListeners();
-
- this._handleAutoFocus();
-
- if (this._options.OPEN_EVENT) {
- var event = createEvent('open', 'globalExpander', {
- bubbles: false
- });
-
- this._triggerEl.dispatchEvent(event);
- }
- }
- }, {
- key: "close",
- value: function close() {
- if (!this._isOpen) {
- return;
- }
-
- this._isOpen = false;
-
- this._updateTriggerLabel();
-
- this._updateAttributes();
-
- this._updateClassAttributes();
-
- this._removeTemporaryEventListeners();
- }
- }, {
- key: "init",
- value: function init() {
- if (this._triggerEl.tagName === 'A' && this._triggerEl.getAttribute('href').charAt(0) === '#') {
- // eslint-disable-next-line no-script-url
- this._triggerEl.setAttribute('href', 'javascript:;');
-
- this._triggerEl.setAttribute('role', 'button');
- } // Warn screen reader users when you are stealing focus
-
-
- if (this._options.AUTOFOCUS) {
- this._triggerEl.setAttribute('aria-haspopup', 'true');
- }
-
- this._updateTriggerLabel();
-
- this._updateAttributes();
-
- this._updateClassAttributes();
-
- this._triggerEl.addEventListener('click', this._handleButtonClick);
-
- this._triggerEl.addEventListener('keydown', this._handleButtonKeydown);
- }
- }]);
-
- return Expander;
- }();
-
- var generateParameters = function generateParameters(value) {
- var parameters = new URL(document.location).searchParams;
- parameters.set('order', value);
- return parameters;
- };
-
- var sortBy = function sortBy() {
- var trigger = document.querySelector('[data-sort-by-trigger]');
- var targetElement = document.querySelector('[data-sort-by-target]');
- var radios = makeArray(document.querySelectorAll('[data-sort-by-radio]'));
-
- if (!trigger || !targetElement || !radios) {
- return;
- }
-
- radios.forEach(function (element) {
- element.addEventListener('click', function () {
- var value = element.querySelector('input').value;
- var parameters = generateParameters(value);
- window.location.replace('/search?' + parameters);
- });
- });
- trigger.setAttribute('role', 'button');
- trigger.insertAdjacentElement('afterend', targetElement);
- targetElement.classList.add('has-tethered');
- var expander = new Expander(trigger, targetElement);
- expander.init();
- return expander;
- };
-
- sortBy();
+
diff --git a/toolkits/nature/packages/nature-sort-by/demo/main.js b/toolkits/nature/packages/nature-sort-by/demo/main.js
index d75cabd27..dfc247ec4 100644
--- a/toolkits/nature/packages/nature-sort-by/demo/main.js
+++ b/toolkits/nature/packages/nature-sort-by/demo/main.js
@@ -1,3 +1,3 @@
-import {sortBy} from '../js/sort-by';
+import {sortBy} from '../js/sort-by.js';
sortBy();
diff --git a/toolkits/nature/packages/nature-sort-by/js/sort-by.js b/toolkits/nature/packages/nature-sort-by/js/sort-by.js
index 6081939e7..1a88970f1 100644
--- a/toolkits/nature/packages/nature-sort-by/js/sort-by.js
+++ b/toolkits/nature/packages/nature-sort-by/js/sort-by.js
@@ -1,39 +1,20 @@
-import {Expander} from '@springernature/global-expander/js/expander';
import {makeArray} from '@springernature/global-javascript/src/helpers';
-const generateParameters = value => {
- const parameters = new URL(document.location).searchParams;
- parameters.set('order', value);
- return parameters;
-};
-
-const sortBy = () => {
- const trigger = document.querySelector('[data-sort-by-trigger]');
- const targetElement = document.querySelector('[data-sort-by-target]');
- const radios = makeArray(document.querySelectorAll('[data-sort-by-radio]'));
-
- if (!trigger || !targetElement || !radios) {
- return;
- }
-
- radios.forEach(element => {
- element.addEventListener('click', () => {
- const value = element.querySelector('input').value;
- const parameters = generateParameters(value);
- window.location.replace('/search?' + parameters);
+function addInputListeners(inputs, submitButton) {
+ inputs.forEach(input => {
+ input.addEventListener('change', () => {
+ submitButton.click();
});
});
-
- trigger.setAttribute('role', 'button');
-
- trigger.insertAdjacentElement('afterend', targetElement);
- targetElement.classList.add('has-tethered');
-
- const expander = new Expander(trigger, targetElement);
-
- expander.init();
-
- return expander;
-};
+}
+
+function sortBy() {
+ const forms = makeArray(document.querySelectorAll('[data-sort-by]'));
+ forms.forEach(form => {
+ const radioInputs = makeArray(form.querySelectorAll('[data-sort-by-input]'));
+ const submitButton = form.querySelector('[data-sort-by-button]');
+ addInputListeners(radioInputs, submitButton);
+ });
+}
export {sortBy};
diff --git a/toolkits/nature/packages/nature-sort-by/package.json b/toolkits/nature/packages/nature-sort-by/package.json
index ab6aac10a..70858cb61 100644
--- a/toolkits/nature/packages/nature-sort-by/package.json
+++ b/toolkits/nature/packages/nature-sort-by/package.json
@@ -1,13 +1,13 @@
{
"name": "@springernature/nature-sort-by",
- "version": "2.1.0",
+ "version": "3.0.0",
"license": "MIT",
"description": "Nature sort by ui dropdown",
"keywords": [],
"author": "Springer Nature",
- "brandContext": "^18.0.0",
+ "brandContext": "^20.0.0",
"dependencies": {
- "@springernature/global-javascript": "^3.0.2",
- "@springernature/global-expander": "^4.0.2"
+ "@springernature/global-javascript": "^3.0.3",
+ "@springernature/global-expander": "^4.2.1"
}
}
diff --git a/toolkits/nature/packages/nature-sort-by/scss/10-settings/sort-by.scss b/toolkits/nature/packages/nature-sort-by/scss/10-settings/sort-by.scss
index 413066bce..f10e0e824 100644
--- a/toolkits/nature/packages/nature-sort-by/scss/10-settings/sort-by.scss
+++ b/toolkits/nature/packages/nature-sort-by/scss/10-settings/sort-by.scss
@@ -1,10 +1,3 @@
-$sort-by--menu-background-colour: #000;
-$sort-by--menu-border-colour: greyscale(5);
-$sort-by--menu-text-colour: greyscale(90);
-$sort-by--menu-padding: spacing(16) 0;
-$sort-by--menu-font-size: .875rem;
-$sort-by--menu-line-height: 1.2;
-$sort-by--button-font-weight: 700;
-$sort-by--button-svg-margin: spacing(16);
-$sort-by--button-list-item-spacing: spacing(8) spacing(16);
-$sort-by--menu-min-width: 180px;
+$sort-by--font-size: $context--font-size-xs;
+$sort-by--line-height: $context--line-height-tight;
+$sort-by--heading-font-weight: $context--font-weight-bold;
diff --git a/toolkits/nature/packages/nature-sort-by/scss/50-components/sort-by.scss b/toolkits/nature/packages/nature-sort-by/scss/50-components/sort-by.scss
index c04ee1c68..f38026f2b 100644
--- a/toolkits/nature/packages/nature-sort-by/scss/50-components/sort-by.scss
+++ b/toolkits/nature/packages/nature-sort-by/scss/50-components/sort-by.scss
@@ -1,69 +1,59 @@
.c-sort-by {
- position: relative;
+ font-size: $sort-by--font-size;
+ line-height: $sort-by--line-height;
+}
- a {
- @include u-link-inherit();
- }
+.c-sort-by__heading {
+ font-weight: $sort-by--heading-font-weight;
+ margin-bottom: spacing(8);
}
-.c-sort-by__menu {
- background-color: $sort-by--menu-background-colour;
- border-bottom: 1px solid $sort-by--menu-border-colour; // non-js
- color: $sort-by--menu-text-colour;
- padding: $sort-by--menu-padding;
- font-size: $sort-by--menu-font-size;
- line-height: $sort-by--menu-line-height;
+.c-sort-by__input-container {
+ margin-bottom: spacing(8);
}
-.c-sort-by__button {
- font-weight: $sort-by--button-font-weight;
- display: flex;
- justify-content: space-between;
- align-items: center;
+[class~='c-sort-by__input-container']:last-of-type {
+ margin-bottom: 0;
+}
- > svg {
- margin-left: $sort-by--button-svg-margin;
- transition-duration: .2s;
- }
+.c-sort-by__input {
+ vertical-align: middle;
+ margin-top: -1px;
+}
- &.is-open > svg {
- transform: rotate(180deg);
- }
+.c-sort-by__button {
+ margin-top: spacing(8);
}
-.c-sort-by__list {
- @include u-list-reset;
- padding: 0;
- margin: 0;
+.js .c-sort-by__button {
+ display: none;
}
-.c-sort-by__list-item {
- padding: $sort-by--button-list-item-spacing;
+@include media-query('md') {
+ .c-sort-by {
+ display: flex;
+ align-items: center;
+ }
- & > label {
- cursor: pointer;
+ .c-sort-by__heading {
+ float: left; // needed because browsers have special rendering rules for legend elements: https://stackoverflow.com/questions/5818960/why-wont-my-legend-element-display-inline
+ margin-bottom: 0;
}
-}
-/**
- JavaScript Enhancements
- */
+ .c-sort-by__input-container {
+ margin-bottom: 0;
+ }
-.c-sort-by__menu.has-tethered {
- position: absolute;
- top: 100%; // bottom of the tether element
- transform: translateY(4px);
- z-index: 1;
- left: 0;
- width: 100%;
- border-radius: 0 0 2px 2px;
- border-bottom: 0;
+ .c-sort-by__input {
+ margin-left: spacing(16);
+ }
- @include media-query('sm') {
- width: auto;
+ .c-sort-by__label {
+ padding-left: spacing(4);
}
- @include media-query('md') {
- min-width: $sort-by--menu-min-width;
+ .c-sort-by__button {
+ margin-left: spacing(16);
+ margin-top: 0;
}
}
diff --git a/toolkits/nature/packages/nature-sort-by/view/sort-by.hbs b/toolkits/nature/packages/nature-sort-by/view/sort-by.hbs
index 89c5c7e8f..f58cc157e 100644
--- a/toolkits/nature/packages/nature-sort-by/view/sort-by.hbs
+++ b/toolkits/nature/packages/nature-sort-by/view/sort-by.hbs
@@ -1,28 +1,10 @@
-