diff --git a/block-languages/benenson-blocks.pot b/block-languages/benenson-blocks.pot index 00e5518..eac41b4 100644 --- a/block-languages/benenson-blocks.pot +++ b/block-languages/benenson-blocks.pot @@ -848,6 +848,87 @@ msgstr "" msgid "Add a repeatable logo block" msgstr "" +#: src/scripts/blocks/logo-slider/DisplayComponent.js:139 +msgid "Are you sure you want to delete this logo from the slider?" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:200 +#: src/scripts/blocks/slider/DisplayComponent.js:217 +msgid "Show Arrows" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:205 +msgid "Number of logos to show per slide" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:212 +#: src/scripts/blocks/slider/DisplayComponent.js:238 +msgid "Slide Options" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:214 +msgid "Slide URL" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:219 +msgid "Open link in new tab" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:247 +msgid "Add a logo below." +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:261 +msgid "Delete Logo" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:262 +msgid "Logo settings" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:277 +msgid "Pevious Logo" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:279 +msgid "Add Logo" +msgstr "" + +#: src/scripts/blocks/logo-slider/DisplayComponent.js:281 +msgid "Next Logo" +msgstr "" + +#: src/scripts/blocks/logo-slider/index.js:27 +msgid "Logo slider" +msgstr "" + +#: src/scripts/blocks/logo-slider/index.js:31 +#: src/scripts/blocks/slider/index.js:38 +msgid "Slider" +msgstr "" + +#: src/scripts/blocks/logo-slider/index.js:32 +#: src/scripts/blocks/slider/index.js:39 +msgid "Carousel" +msgstr "" + +#: src/scripts/blocks/logo-slider/index.js:33 +#: src/scripts/blocks/slider/index.js:40 +msgid "Scroller" +msgstr "" + +#: src/scripts/blocks/logo-slider/index.js:69 +#: src/scripts/blocks/slider/DisplayComponent.js:287 +#: src/scripts/blocks/slider/index.js:59 +msgid "Next" +msgstr "" + +#: src/scripts/blocks/logo-slider/index.js:70 +#: src/scripts/blocks/slider/DisplayComponent.js:288 +#: src/scripts/blocks/slider/index.js:60 +msgid "Previous" +msgstr "" + #: src/scripts/blocks/media-aside/DisplayComponent.js:103 msgid "(Insert Link text)" msgstr "" @@ -1060,10 +1141,6 @@ msgstr "" msgid "Do you wish to delete this slide? This action is irreversible" msgstr "" -#: src/scripts/blocks/slider/DisplayComponent.js:217 -msgid "Show Arrows" -msgstr "" - #: src/scripts/blocks/slider/DisplayComponent.js:223 msgid "Has Content" msgstr "" @@ -1085,10 +1162,6 @@ msgid "" "you to navigate through each slide." msgstr "" -#: src/scripts/blocks/slider/DisplayComponent.js:238 -msgid "Slide Options" -msgstr "" - #: src/scripts/blocks/slider/DisplayComponent.js:240 msgid "Slide Title" msgstr "" @@ -1116,16 +1189,6 @@ msgstr "" msgid "This is irreversible." msgstr "" -#: src/scripts/blocks/slider/DisplayComponent.js:287 -#: src/scripts/blocks/slider/index.js:59 -msgid "Next" -msgstr "" - -#: src/scripts/blocks/slider/DisplayComponent.js:288 -#: src/scripts/blocks/slider/index.js:60 -msgid "Previous" -msgstr "" - #: src/scripts/blocks/slider/DisplayComponent.js:294 msgid "Add a slide below." msgstr "" @@ -1162,18 +1225,6 @@ msgstr "" msgid "Toggle Content" msgstr "" -#: src/scripts/blocks/slider/index.js:38 -msgid "Slider" -msgstr "" - -#: src/scripts/blocks/slider/index.js:39 -msgid "Carousel" -msgstr "" - -#: src/scripts/blocks/slider/index.js:40 -msgid "Scroller" -msgstr "" - #: src/scripts/blocks/tweet/index.js:109 msgid "(Action Title)" msgstr "" diff --git a/src/scripts/app.js b/src/scripts/app.js index 22a140f..66f29dc 100755 --- a/src/scripts/app.js +++ b/src/scripts/app.js @@ -11,6 +11,7 @@ import categorySlider from './modules/category-slider'; import modalEmbed from './modules/modal-embed'; import inlineEmbed from './modules/inline-embed'; import sliderBlock from './modules/slider-block'; +import logoSlider from './modules/logo-slider'; import subcatDrops from './modules/subcategory-dropdown'; import categoryExpander from './modules/category-expander'; import fluidText from './modules/fluid-text'; @@ -31,6 +32,7 @@ const App = () => { modalEmbed(); inlineEmbed(); sliderBlock(); + logoSlider(); subcatDrops(); categoryExpander(); scrollTo(); diff --git a/src/scripts/blocks.js b/src/scripts/blocks.js index a99adc8..0b32e10 100755 --- a/src/scripts/blocks.js +++ b/src/scripts/blocks.js @@ -26,6 +26,7 @@ import './blocks/download'; import './blocks/key-facts'; import './blocks/category-list'; import './blocks/logo-list'; +import './blocks/logo-slider'; import './blocks/link'; import './blocks/media-aside'; diff --git a/src/scripts/blocks/logo-slider/DisplayComponent.js b/src/scripts/blocks/logo-slider/DisplayComponent.js new file mode 100644 index 0000000..324640a --- /dev/null +++ b/src/scripts/blocks/logo-slider/DisplayComponent.js @@ -0,0 +1,289 @@ +import classnames from 'classnames'; + +const randId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10); + +const { __ } = wp.i18n; +const { Component, Fragment } = wp.element; +const { applyFilters } = wp.hooks; +const { + PanelBody, Button, TextControl, ToggleControl, RangeControl, +} = wp.components; +const { + InspectorControls, RichText, BlockIcon, URLInputButton, +} = wp.editor; +const { PostMediaSelector } = benenson.components; + +class DisplayComponent extends Component { + static emptySlide = { + id: '', + title: '', + imageId: '', + imageUrl: '', + imageLink: '', + newTab: false, + }; + + constructor(...props) { + super(...props); + + this.state = { + selectedSlide: 0, + group: this.props.attributes.perSlide, + }; + } + + componentDidMount() { + const { attributes, setAttributes } = this.props; + + if (!attributes.sliderId) { + setAttributes({ + sliderId: randId(), + }); + } + } + + componentDidUpdate(prevProps) { + if (prevProps.attributes.perSlide !== this.props.attributes.perSlide) { + this.setState({ + group: this.props.attributes.perSlide, + }); + } + } + + /** + * Higher order component that takes the attribute key, + * this then returns a function which takes a value, + * when called it updates the attribute with the key. + * @param key + * @returns {function(*): *} + */ + createUpdateAttribute = key => value => this.props.setAttributes({ [key]: value }); + + createUpdateSlideAttribute = + index => + key => + value => + this.props.setAttributes({ + slides: [ + ...this.props.attributes.slides + .slice(0, Math.max(0, index)), + { + ...this.props.attributes.slides[index], + [key]: value, + }, + ...this.props.attributes + .slides.slice(index + 1, this.props.attributes.slides.length), + ], + }); + + createUpdateImage = + index => + ({ + id: imageId = false, + source_url: imageUrl = false, + media_details: { + sizes, + } = {}, + } = {}) => { + this.props.setAttributes({ + slides: [ + ...this.props.attributes.slides + .slice(0, Math.max(0, index)), + { + ...this.props.attributes.slides[index], + imageId, + imageUrl, + sizes, + }, + ...this.props.attributes + .slides.slice(index + 1, this.props.attributes.slides.length), + ], + }); + }; + + addSlide = () => { + const { attributes, setAttributes } = this.props; + + setAttributes({ + slides: [...attributes.slides, { + ...DisplayComponent.emptySlide, + id: randId(), + }], + }); + + this.setState({ + selectedSlide: attributes.slides.length, + group: attributes.slides.length + 1, + }); + }; + + deleteSlide = (index) => { + const { attributes, setAttributes } = this.props; + + if (index === attributes.slides.length - 1) { + this.setState({ + selectedSlide: index - 1, + group: (index - 1) > attributes.perSlide ? attributes.slides.length : attributes.perSlide, + }); + } + + this.props.setAttributes({ + slides: [ + ...attributes.slides.slice(0, Math.max(0, index)), + ...attributes.slides.slice(index + 1, attributes.slides.length), + ], + }); + }; + + initiateDelete = () => { + if (confirm(__('Are you sure you want to delete this logo from the slider?'))) { // eslint-disable-line no-restricted-globals, no-alert + this.deleteSlide(this.state.selectedSlide); + } + }; + + selectSlide = index => this.setState({ + selectedSlide: index, + }); + + createSelectSlide = index => () => this.selectSlide(index); + + movePrev = () => { + const { selectedSlide, group } = this.state; + + const slideOrder = [...this.props.attributes.slides]; + const temp = slideOrder[selectedSlide]; + slideOrder[selectedSlide] = slideOrder[selectedSlide - 1]; + slideOrder[selectedSlide - 1] = temp; + + this.props.setAttributes({ + slides: slideOrder, + }); + + this.setState({ + selectedSlide: selectedSlide - 1, + group: (selectedSlide - 1) < this.props.attributes.perSlide ? this.props.attributes.perSlide : group - 1, // eslint-disable-line max-len + }); + }; + + moveNext = () => { + const { selectedSlide, group } = this.state; + const slideOrder = [...this.props.attributes.slides]; + const temp = slideOrder[selectedSlide]; + slideOrder[selectedSlide] = slideOrder[selectedSlide + 1]; + slideOrder[selectedSlide + 1] = temp; + + this.props.setAttributes({ + slides: slideOrder, + }); + + this.setState({ + selectedSlide: selectedSlide + 1, + group: (selectedSlide + 1) === (this.props.attributes.slides.length - this.props.attributes.perSlide) ? this.props.attributes.perSlide : group + 1, // eslint-disable-line max-len + }); + }; + + render() { + const { attributes, setAttributes } = this.props; + const { selectedSlide, group } = this.state; + + const currentSlide = attributes.slides[selectedSlide]; + const updateSlide = this.createUpdateSlideAttribute(selectedSlide); + + const quantityOptions = applyFilters('benenson.block.logoSlider.quantityOptions', { + min: 1, + max: 10, + }); + + const controls = ( + + + + { currentSlide && ( + + + + { attributes.slides.length >= 2 && ( +

Change milestone position:

+ )} + + +
+ ) } +
+ ); + + const sliderClasses = classnames( + 'logoSlider', + `logoSlider-${attributes.perSlide}perSlide`, + ); + + return ( + + { controls } +
+
+
+ { attributes.slides.length === 0 && ( +
+
+

{ __('Add a logo below.', 'benenson') }

+ +
+
+ ) } + { attributes.slides.length > 0 && attributes.slides.map((slide, index) => { + if (index >= group - attributes.perSlide && index <= group - 1) { + const classes = classnames('logoSlide', { + 'is-active': index === selectedSlide, + }); + + return ( +
+
+ + +
+ +
+ ); + } + + return null; + }) } +
+
+ +
+
+ ); + } +} + +export default DisplayComponent; diff --git a/src/scripts/blocks/logo-slider/index.js b/src/scripts/blocks/logo-slider/index.js new file mode 100644 index 0000000..682bde5 --- /dev/null +++ b/src/scripts/blocks/logo-slider/index.js @@ -0,0 +1,76 @@ +import classnames from 'classnames'; +import DisplayComponent from './DisplayComponent'; + +const { __ } = wp.i18n; +const { registerBlockType } = wp.blocks; +const { RichText } = wp.editor; + +const blockAttributes = { + sliderId: { + type: 'string', + }, + slides: { + type: 'array', + default: [], + }, + hasArrows: { + type: 'boolean', + default: true, + }, + perSlide: { + type: 'interger', + default: 4, + }, +}; + +registerBlockType('benenson/block-logo-slider', { + title: __('Logo slider', 'benenson'), + icon: 'images-alt2', + category: 'benenson', + keywords: [ + __('Slider', 'benenson'), + __('Carousel', 'benenson'), + __('Scroller', 'benenson'), + ], + attributes: blockAttributes, + + edit: DisplayComponent, + + save: ({ attributes, className }) => { + const { slides, logoSliderId } = attributes; + const sliderClasses = classnames( + 'logoSlider', + `logoSlider-${attributes.perSlide}perSlide`, + ); + + if (slides.length < 1) { + return null; + } + + return ( +
+
+
+ { slides.map((slide, index) => ( +
+
+ { slide.imageLink !== '' ? + + + + : + + } +
+
+ )) } +
+ { attributes.hasArrows && [ + , + , + ] } +
+
+ ); + }, +}); diff --git a/src/scripts/modules/logo-slider.js b/src/scripts/modules/logo-slider.js new file mode 100644 index 0000000..22d8d24 --- /dev/null +++ b/src/scripts/modules/logo-slider.js @@ -0,0 +1,35 @@ +import Flickity from 'flickity'; +import 'flickity-as-nav-for'; +import { debounce } from 'lodash-es'; + +const createSlider = (slider) => { + const slides = slider.querySelector('.logoSlides'); + + if (!slides) { + return; + } + + const isRightToLeft = document.documentElement.getAttribute('dir') === 'rtl'; + + const prevArrow = slider.querySelector('.logoSlider-arrow--previous'); + const nextArrow = slider.querySelector('.logoSlider-arrow--next'); + + const slidesInstance = new Flickity(slides, { + rightToLeft: isRightToLeft, + prevNextButtons: false, + pageDots: false, + contain: true, + }); + + if (nextArrow && prevArrow) { + prevArrow.addEventListener('click', () => slidesInstance.previous()); + nextArrow.addEventListener('click', () => slidesInstance.next()); + } +}; + +const init = () => { + const sliders = Array.from(document.querySelectorAll('.logoSlider')); + sliders.forEach(createSlider); +}; + +export default init; diff --git a/src/styles/components/logo-slider/_editor.scss b/src/styles/components/logo-slider/_editor.scss new file mode 100644 index 0000000..2a18b42 --- /dev/null +++ b/src/styles/components/logo-slider/_editor.scss @@ -0,0 +1,86 @@ +.logoSlider { + width: 100%; +} + +.logoSlider-nav { + display: flex; + margin-top: 20px; +} + +.logoSlider-navActions .logoSlider-navButton { + width: 100%; + flex-grow: 1; +} + +.logoSlider-navActions .logoSlider-navButton + .logoSlider-navButton { + margin-left: 1px; +} + +.logoSlider-navActions .logoSlider-navButton[disabled] { + cursor: default; + background-color: $color-grey-brand; + border-color: $color-grey-brand; + + &:enter { + color: $color-white; + } +} + +.logoSlides { + display: flex; + flex-wrap: wrap; +} + +.logoSlide { + padding-top: 20px; + position: relative; + border-top: 3px solid transparent; + width: calc(25% - 23px) !important; + flex-basis: calc(25% - 23px) !important; +} + +.logoSlide + .logoSlide { + margin-left: 30px; +} + +.logoSlide:nth-child(4n+1) { + margin-left: 0px; +} + +.logoSlide.is-active { + border-top: 3px solid #4daacf; +} + +.logoSlide .logoSlide-settings { + display: none; + position: absolute; + right: 0; + top: 5px; +} + +.logoSlide:enter .logoSlide-settings { + display: block; +} + +.logoSlide-deleteBtn { + margin-bottom: 5px; + background-color: #c92c2c; + color: $color-white; + + &:enter { + background-color: #c92c2c !important; + box-shadow: none !important; + color: $color-white !important; + } +} + +.logoSlide-settings .components-icon-button:not(:disabled):not([aria-disabled=true]):not(.is-default):hover { + background-color: #4daacf; + color: $color-white; + box-shadow: none; +} + +.logoSlide .editor-url-input__button { + position: relative; + z-index: 1; +} diff --git a/src/styles/components/logo-slider/_main.scss b/src/styles/components/logo-slider/_main.scss new file mode 100644 index 0000000..80c097c --- /dev/null +++ b/src/styles/components/logo-slider/_main.scss @@ -0,0 +1,80 @@ +.logoSlides-container { + position: relative; +} + +.logo-Slides { + padding: 0 50px; +} + +@for $i from 1 through 10 { + .logoSlider-#{$i}perSlide .logoSlide { + width: 100%; + flex-basis: 100%; + + @include mq(small) { + width: #{100 / $i + "%"}; + flex-basis: #{100 / $i + "%"}; + } + } +} + +.logoSlide .logoSlide-contentContainer { + text-align: center; +} + +.logoSlider-arrow { + position: absolute; + top: 50%; + left: 0; + background-color: $color-white; + width: 50px; + height: 50px; + overflow: hidden; + text-indent: -1000vw; + border: none; + transform: translateY(-50%); + z-index: 10; + cursor: pointer; +} + +.logoSlider-arrow::after { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.logoSlider-arrow--next { + right: 0; + left: initial; + + .rtl & { + right: initial; + left: 0; + } +} + +.rtl .logoSlider-arrow--previous { + right: 0; + left: initial; +} + +.logoSlider-arrow--next::after { + @include icon_svg(30px, 30px, "black", "arrow-ios-forward-outline" ); + content: ""; + display: block; + + .rtl & { + @include icon_svg(30px, 30px, "black", "arrow-ios-back-outline" ); + } +} + +.logoSlider-arrow--previous::after { + @include icon_svg(30px, 30px, "black", "arrow-ios-back-outline" ); + content: ""; + display: block; + + .rtl & { + @include icon_svg(30px, 30px, "black", "arrow-ios-forward-outline" ); + } +} diff --git a/src/styles/style-editor.scss b/src/styles/style-editor.scss index c7c9282..4bdadb6 100755 --- a/src/styles/style-editor.scss +++ b/src/styles/style-editor.scss @@ -81,6 +81,8 @@ @import "components/header/editor"; @import "components/logo-list/main"; @import "components/logo-list/editor"; +@import "components/logo-slider/main"; +@import "components/logo-slider/editor"; @import "components/post-grid/main"; @import "components/post-grid/editor"; @import "components/split-grid/editor"; diff --git a/src/styles/style.scss b/src/styles/style.scss index 5294582..60c091d 100755 --- a/src/styles/style.scss +++ b/src/styles/style.scss @@ -97,6 +97,7 @@ @import "components/header/main"; @import "components/category-list/main"; @import "components/logo-list/main"; +@import "components/logo-slider/main"; @import "components/post-grid/main"; @import "components/media-aside/main";