[IMP] website: review snippet tabs

Adding new options that are available through Bootstrap (Tabs or pills
class for nav-item. Alignment, menu horizontal/vertical, etc). Adding a
slide transition option. The snippet becomes also a drag&drop zone.

Part of https://github.com/odoo/odoo/pull/42937
task-2088157
task-2152185
This commit is contained in:
Benjamin Vray
2020-01-24 13:02:01 +00:00
committed by qsm-odoo
parent ca5f9ce26d
commit 7572cc150d
7 changed files with 324 additions and 183 deletions
@@ -393,119 +393,6 @@ options.registry.CarouselItem = options.Class.extend({
},
});
options.registry.navTabs = options.Class.extend({
/**
* @override
*/
start: function () {
this._findLinksAndPanes();
return this._super.apply(this, arguments);
},
/**
* @override
*/
onBuilt: function () {
this._generateUniqueIDs();
},
/**
* @override
*/
onClone: function () {
this._generateUniqueIDs();
},
//--------------------------------------------------------------------------
// Options
//--------------------------------------------------------------------------
/**
* Creates a new tab and tab-pane.
*
* @see this.selectClass for parameters
*/
addTab: function (previewMode, widgetValue, params) {
var $activeItem = this.$navLinks.filter('.active').parent();
var $activePane = this.$tabPanes.filter('.active');
var $navItem = $activeItem.clone();
var $navLink = $navItem.find('.nav-link').removeClass('active show');
var $tabPane = $activePane.clone().removeClass('active show');
$navItem.insertAfter($activeItem);
$tabPane.insertAfter($activePane);
this._findLinksAndPanes();
this._generateUniqueIDs();
$navLink.tab('show');
},
/**
* Removes the current active tab and its content.
*
* @see this.selectClass for parameters
*/
removeTab: function (previewMode, widgetValue, params) {
var self = this;
var $activeLink = this.$navLinks.filter('.active');
var $activePane = this.$tabPanes.filter('.active');
var $next = this.$navLinks.eq((this.$navLinks.index($activeLink) + 1) % this.$navLinks.length);
return new Promise(resolve => {
$next.one('shown.bs.tab', function () {
$activeLink.parent().remove();
$activePane.remove();
self._findLinksAndPanes();
resolve();
});
$next.tab('show');
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_computeWidgetVisibility: async function (widgetName, params) {
if (widgetName === 'remove_tab_opt') {
return (this.$tabPanes.length > 2);
}
return this._super(...arguments);
},
/**
* @private
*/
_findLinksAndPanes: function () {
this.$navLinks = this.$target.find('.nav-link');
var $el = this.$target;
do {
$el = $el.parent();
this.$tabPanes = $el.find('.tab-pane');
} while (this.$tabPanes.length === 0 && !$el.is('body'));
},
/**
* @private
*/
_generateUniqueIDs: function () {
for (var i = 0; i < this.$navLinks.length; i++) {
var id = _.now() + '_' + _.uniqueId();
var idLink = 'nav_tabs_link_' + id;
var idContent = 'nav_tabs_content_' + id;
this.$navLinks.eq(i).attr({
'id': idLink,
'href': '#' + idContent,
'aria-controls': idContent,
});
this.$tabPanes.eq(i).attr({
'id': idContent,
'aria-labelledby': idLink,
});
}
},
});
options.registry.sizing_x = options.registry.sizing.extend({
/**
* @override
@@ -1171,6 +1058,17 @@ options.registry.SnippetMove = options.Class.extend({
return this._super(...arguments);
},
/**
* @override
*/
onFocus: function () {
// TODO improve this: hack to hide options section if snippet move is
// the only one.
const $allOptions = this.$el.parent();
if ($allOptions.find('we-customizeblock-option').length <= 1) {
$allOptions.addClass('d-none');
}
},
//--------------------------------------------------------------------------
// Options
@@ -1182,12 +1080,20 @@ options.registry.SnippetMove = options.Class.extend({
* @see this.selectClass for parameters
*/
moveSnippet: function (previewMode, widgetValue, params) {
const isNavItem = this.$target[0].classList.contains('nav-item');
const $tabPane = isNavItem ? $(this.$target.find('.nav-link')[0].hash) : null;
switch (widgetValue) {
case 'prev':
this.$target.prev().before(this.$target);
if (isNavItem) {
$tabPane.prev().before($tabPane);
}
break;
case 'next':
this.$target.next().after(this.$target);
if (isNavItem) {
$tabPane.next().after($tabPane);
}
break;
}
},
@@ -631,7 +631,6 @@ table.table_desc tr td {
}
}
//
// Layout
//
@@ -0,0 +1,25 @@
// Tabs
.s_tabs[data-vcss="001"] {
.s_tabs_content {
&.s_tabs_slide_up, &.s_tabs_slide_down, &.s_tabs_slide_left, &.s_tabs_slide_right {
> .tab-pane.fade {
transition: all 0.2s;
}
> .tab-pane.fade.show {
transform: translateX(0rem) translateY(0rem);
}
}
&.s_tabs_slide_up > .tab-pane.fade {
transform: translateY(-1rem);
}
&.s_tabs_slide_down > .tab-pane.fade {
transform: translateY(1rem);
}
&.s_tabs_slide_left > .tab-pane.fade {
transform: translateX(-1rem);
}
&.s_tabs_slide_right > .tab-pane.fade {
transform: translateX(1rem);
}
}
}
@@ -0,0 +1,167 @@
odoo.define('website.s_tabs_options', function (require) {
'use strict';
const options = require('web_editor.snippets.options');
options.registry.NavTabs = options.Class.extend({
isTopOption: true,
/**
* @override
*/
start: function () {
this._findLinksAndPanes();
return this._super.apply(this, arguments);
},
/**
* @override
*/
onBuilt: function () {
this._generateUniqueIDs();
},
/**
* @override
*/
onClone: function () {
this._generateUniqueIDs();
},
//--------------------------------------------------------------------------
// Options
//--------------------------------------------------------------------------
/**
* Creates a new tab and tab-pane.
*
* @see this.selectClass for parameters
*/
addTab: function (previewMode, widgetValue, params) {
var $activeItem = this.$navLinks.filter('.active').parent();
var $activePane = this.$tabPanes.filter('.active');
var $navItem = $activeItem.clone();
var $navLink = $navItem.find('.nav-link').removeClass('active show');
var $tabPane = $activePane.clone().removeClass('active show');
$navItem.insertAfter($activeItem);
$tabPane.insertAfter($activePane);
this._findLinksAndPanes();
this._generateUniqueIDs();
$navLink.tab('show');
},
/**
* Removes the current active tab and its content.
*
* @see this.selectClass for parameters
*/
removeTab: function (previewMode, widgetValue, params) {
var self = this;
var $activeLink = this.$navLinks.filter('.active');
var $activePane = this.$tabPanes.filter('.active');
var $next = this.$navLinks.eq((this.$navLinks.index($activeLink) + 1) % this.$navLinks.length);
return new Promise(resolve => {
$next.one('shown.bs.tab', function () {
$activeLink.parent().remove();
$activePane.remove();
self._findLinksAndPanes();
resolve();
});
$next.tab('show');
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_computeWidgetVisibility: async function (widgetName, params) {
if (widgetName === 'remove_tab_opt') {
return (this.$tabPanes.length > 2);
}
return this._super(...arguments);
},
/**
* @private
*/
_findLinksAndPanes: function () {
this.$navLinks = this.$target.find('.nav:first .nav-link');
this.$tabPanes = this.$target.find('.tab-content:first .tab-pane');
},
/**
* @private
*/
_generateUniqueIDs: function () {
for (var i = 0; i < this.$navLinks.length; i++) {
var id = _.now() + '_' + _.uniqueId();
var idLink = 'nav_tabs_link_' + id;
var idContent = 'nav_tabs_content_' + id;
this.$navLinks.eq(i).attr({
'id': idLink,
'href': '#' + idContent,
'aria-controls': idContent,
});
this.$tabPanes.eq(i).attr({
'id': idContent,
'aria-labelledby': idLink,
});
}
},
});
options.registry.NavTabsStyle = options.Class.extend({
//--------------------------------------------------------------------------
// Options
//--------------------------------------------------------------------------
/**
* Set the style of the tabs.
*
* @see this.selectClass for parameters
*/
setStyle: function (previewMode, widgetValue, params) {
const $nav = this.$target.find('.s_tabs_nav:first .nav');
const isPills = widgetValue === 'pills';
$nav.toggleClass('nav-tabs card-header-tabs', !isPills);
$nav.toggleClass('nav-pills', isPills);
this.$target.find('.s_tabs_nav:first').toggleClass('card-header', !isPills).toggleClass('mb-3', isPills);
this.$target.toggleClass('card', !isPills);
this.$target.find('.s_tabs_content:first').toggleClass('card-body', !isPills);
},
/**
* Horizontal/vertical nav.
*
* @see this.selectClass for parameters
*/
setDirection: function (previewMode, widgetValue, params) {
const isVertical = widgetValue === 'vertical';
this.$target.toggleClass('row s_col_no_resize s_col_no_bgcolor', isVertical);
this.$target.find('.s_tabs_nav:first .nav').toggleClass('flex-column', isVertical);
this.$target.find('.s_tabs_nav:first > .nav-link').toggleClass('py-2', isVertical);
this.$target.find('.s_tabs_nav:first').toggleClass('col-md-3', isVertical);
this.$target.find('.s_tabs_content:first').toggleClass('col-md-9', isVertical);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_computeWidgetState: function (methodName, params) {
switch (methodName) {
case 'setStyle':
return this.$target.find('.s_tabs_nav:first .nav').hasClass('nav-pills') ? 'pills' : 'tabs';
case 'setDirection':
return this.$target.find('.s_tabs_nav:first .nav').hasClass('flex-column') ? 'vertical' : 'horizontal';
}
return this._super(...arguments);
},
});
});
+1
View File
@@ -115,6 +115,7 @@
<script type="text/javascript" src="/website/static/src/snippets/s_product_catalog/options.js"/>
<script type="text/javascript" src="/website/static/src/snippets/s_chart/options.js"/>
<script type="text/javascript" src="/website/static/src/snippets/s_rating/options.js"/>
<script type="text/javascript" src="/website/static/src/snippets/s_tabs/options.js"/>
<script type="text/javascript" src="/website/static/src/js/editor/wysiwyg_multizone.js"/>
<script type="text/javascript" src="/website/static/src/js/editor/wysiwyg_multizone_translate.js"/>
+109 -61
View File
@@ -2,68 +2,58 @@
<odoo>
<template id="s_tabs" name="Tabs">
<section class="s_tabs">
<section class="s_tabs pt48 pb48" data-vcss="001">
<div class="container">
<div class="row s_col_no_bgcolor">
<div class="col-lg-8 offset-lg-2 pt48 pb48">
<div class="card bg-white">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active"
id="nav_tabs_link_1"
data-toggle="tab"
href="#nav_tabs_content_1"
role="tab"
aria-controls="nav_tabs_content_1"
aria-selected="true">Home</a>
</li>
<li class="nav-item">
<a class="nav-link"
id="nav_tabs_link_2"
data-toggle="tab"
href="#nav_tabs_content_2"
role="tab"
aria-controls="nav_tabs_content_2"
aria-selected="false">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link"
id="nav_tabs_link_3"
data-toggle="tab"
href="#nav_tabs_content_3"
role="tab"
aria-controls="nav_tabs_content_3"
aria-selected="false">Contact</a>
</li>
</ul>
</div>
<div class="card-body tab-content">
<div class="tab-pane fade show active"
id="nav_tabs_content_1"
role="tabpanel"
aria-labelledby="nav_tabs_link_1">
<h3>Details</h3>
<p><b>Great stories have personality.</b> Consider telling a great story that provides personality. Writing a story with personality for potential clients will assists with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.</p>
</div>
<div class="tab-pane fade"
id="nav_tabs_content_2"
role="tabpanel"
aria-labelledby="nav_tabs_link_2">
<h3>Shipping</h3>
<p><b>Great stories are for everyone even when only written for just one person.</b> If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
</div>
<div class="tab-pane fade"
id="nav_tabs_content_3"
role="tabpanel"
aria-labelledby="nav_tabs_link_3">
<h3>Review</h3>
<blockquote>
<p>Write a quote here from one of your customers. Quotes are a great way to build confidence in your products or services.</p>
<footer>— Jane DOE, CEO of <b>MyCompany</b></footer>
</blockquote>
</div>
</div>
<div class="s_tabs_main">
<div class="s_tabs_nav mb-3">
<ul class="nav nav-pills justify-content-center" role="tablist">
<li class="nav-item">
<a class="nav-link active"
id="nav_tabs_link_1"
data-toggle="tab"
href="#nav_tabs_content_1"
role="tab"
aria-controls="nav_tabs_content_1"
aria-selected="true">Home</a>
</li>
<li class="nav-item">
<a class="nav-link"
id="nav_tabs_link_2"
data-toggle="tab"
href="#nav_tabs_content_2"
role="tab"
aria-controls="nav_tabs_content_2"
aria-selected="false">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link"
id="nav_tabs_link_3"
data-toggle="tab"
href="#nav_tabs_content_3"
role="tab"
aria-controls="nav_tabs_content_3"
aria-selected="false">Contact</a>
</li>
</ul>
</div>
<div class="s_tabs_content tab-content">
<div class="tab-pane fade show active"
id="nav_tabs_content_1"
role="tabpanel"
aria-labelledby="nav_tabs_link_1">
<div class="oe_structure oe_empty"/>
</div>
<div class="tab-pane fade"
id="nav_tabs_content_2"
role="tabpanel"
aria-labelledby="nav_tabs_link_2">
<div class="oe_structure oe_empty"/>
</div>
<div class="tab-pane fade"
id="nav_tabs_content_3"
role="tabpanel"
aria-labelledby="nav_tabs_link_3">
<div class="oe_structure oe_empty"/>
</div>
</div>
</div>
@@ -71,4 +61,62 @@
</section>
</template>
<template id="s_tabs_options" inherit_id="website.snippet_options">
<xpath expr="." position="inside">
<div data-js="NavTabs" data-selector="section.s_tabs">
<we-button data-add-tab=""
data-no-preview="true"
class="fa fa-fw fa-plus"
title="Add Tab"
aria-label="Add Tab"/>
<we-button data-remove-tab=""
data-name="remove_tab_opt"
data-no-preview="true"
class="fa fa-fw fa-minus"
title="Remove Tab"
aria-label="Remove Tab"/>
</div>
<div data-js="NavTabsStyle" data-selector="section" data-target=".s_tabs_main">
<we-select string="Style">
<we-button data-set-style="tabs" data-name="tabs_opt" data-trigger="horizontal_opt">Tabs</we-button>
<we-button data-set-style="pills" data-name="pills_opt" data-trigger="tabs_color_opt" data-trigger-value="">Buttons</we-button>
</we-select>
<we-colorpicker string="Tabs color"
data-dependencies="tabs_opt"
data-name="tabs_color_opt"
data-select-style="true"
data-css-property="background-color"
data-color-prefix="bg-"/>
<we-select string="Direction" data-dependencies="pills_opt">
<we-button data-set-direction="horizontal" data-name="horizontal_opt">Horizontal</we-button>
<we-button data-set-direction="vertical" data-trigger="left_alignment_opt">Vertical</we-button>
</we-select>
<we-select string="Alignment" data-apply-to=".s_tabs_nav:first .nav" data-dependencies="horizontal_opt">
<we-button data-select-class="" data-name="left_alignment_opt">Left</we-button>
<we-button data-select-class="justify-content-center">Center</we-button>
<we-button data-select-class="justify-content-end">Right</we-button>
</we-select>
<we-select string="Fill and justify" data-apply-to=".s_tabs_nav:first .nav" data-dependencies="horizontal_opt">
<we-button data-select-class="">Regular</we-button>
<we-button data-select-class="nav-fill">Fill</we-button>
<we-button data-select-class="nav-justified">Justified</we-button>
</we-select>
<we-divider/>
<we-select string="Transition" data-apply-to=".s_tabs_content:first">
<we-button data-select-class="">No Slide Effect</we-button>
<we-button data-select-class="s_tabs_slide_up">Slide Up</we-button>
<we-button data-select-class="s_tabs_slide_down">Slide Down</we-button>
<we-button data-select-class="s_tabs_slide_left">Slide Left</we-button>
<we-button data-select-class="s_tabs_slide_right">Slide Right</we-button>
</we-select>
</div>
</xpath>
</template>
<template id="assets_snippet_s_tabs_css_001" inherit_id="website.assets_frontend">
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/website/static/src/snippets/s_tabs/001.scss"/>
</xpath>
</template>
</odoo>
+3 -8
View File
@@ -120,7 +120,7 @@
<t t-call="web_editor.snippet_options"/>
<!-- COLOR, BORDER, SHADOW | .s_three_columns | .s_comparisons -->
<div data-selector=".s_three_columns .row > div, .s_comparisons .row > div, .s_tabs .row > div"
<div data-selector=".s_three_columns .row > div, .s_comparisons .row > div"
data-target=".card">
<we-colorpicker string="Background Color"
data-select-style="true"
@@ -186,11 +186,6 @@
<we-button data-remove-slide="true" data-no-preview="true" class="fa fa-fw fa-minus"/>
</div>
<div data-js="navTabs" data-selector="section .row > div" data-target=".nav-tabs">
<we-button data-add-tab="1" data-no-preview="true">Add Tab</we-button>
<we-button data-remove-tab="1" data-name="remove_tab_opt" data-no-preview="true">Remove Tab</we-button>
</div>
<!-- Accordion -->
<div data-js="collapse"
data-selector='.accordion > .card'
@@ -216,7 +211,7 @@
<we-button class="fa fa-fw fa-angle-down" data-move-snippet="next" data-no-preview="true"/>
</div>
<div data-js="SnippetMove"
data-selector=".row:not(.s_col_no_resize) > div">
data-selector=".row:not(.s_col_no_resize) > div, .nav-item">
<we-button class="fa fa-fw fa-angle-left" data-move-snippet="prev" data-no-preview="true"/>
<we-button class="fa fa-fw fa-angle-right" data-move-snippet="next" data-no-preview="true"/>
</div>
@@ -358,7 +353,7 @@
<!-- Anchor Name -->
<div data-js="anchor"
data-selector=":not(p).oe_structure > *, :not(p)[data-oe-type=html] > *"
data-exclude=".modal *">
data-exclude=".modal *, .oe_structure .oe_structure *, [data-oe-type=html] .oe_structure *">
<we-button class="fa fa-fw fa-link"
title="Create a link to target this section"
data-no-preview="true"/>