[IMP] web: SearchPanel, revamp scss

This commit simplifies the component SCSS improving bootstrap
integration and allowing components other than 'SearchPanel' to use the
same tree-view design.

Part of the overall v16 SCSS optimization/restyle, task-2704984.

task-2795166

closes odoo/odoo#87687

Related: odoo/enterprise#25781
Signed-off-by: Pierre Paridans (app) <app@odoo.com>
This commit is contained in:
stefanorigano (SRI)
2022-08-03 06:34:03 +02:00
committed by Pierre Paridans
parent de498ee63a
commit b3990e7d13
9 changed files with 170 additions and 185 deletions
@@ -159,6 +159,10 @@ $dropdown-link-disabled-color: $o-main-color-muted !default;
$dropdown-item-padding-y: $o-dropdown-vpadding !default;
$dropdown-item-padding-x: $o-dropdown-hpadding !default;
// List group
$list-group-active-color: $o-list-group-active-color !default;
$list-group-active-bg: $o-list-group-active-bg !default;
// Forms
$custom-control-indicator-border-color: $gray-500 !default;
@@ -123,6 +123,12 @@ $o-statusbar-height: 33px !default;
$o-label-font-size-factor: 0.8 !default;
// == List group
$o-list-group-active-color: $o-gray-900 !default;
$o-list-group-active-bg: lighten(saturate(adjust-hue($o-info, 15), 1.8), 50) !default;
// == Badges
// Define a minimum width. This value is arbitrary and strictly font-related.
+42
View File
@@ -66,6 +66,48 @@
animation: catchAttention 200ms ease 0s infinite normal;
}
// ----------------------------------------------------------------------------
// Render a "tree-view" design on a set of vertical elements
// @param {number} --treeEntry-padding-h - Entry's horizzontal padding.
// @param {number} --treeEntry-padding-v - Entry's vertical padding.
// @param {number} --treeEntry--before-top - Vertical-line top position.
// @param {string} --treeEntry--after-display - Horizzontal-line display mode.
// @param {color} --treeEntry--beforeAfter-color - Lines color
// @param {color} --treeEntry--beforeAfter-left - Lines left position
// ----------------------------------------------------------------------------
.o_treeEntry {
$-padding-h: var(--treeEntry-padding-h, #{map-get($spacers, 4)});
$-padding-v: var(--treeEntry-padding-v, #{map-get($spacers, 2)});
padding-left: $-padding-h;
position: relative;
&:before, &:after {
position: absolute;
left: var(--treeEntry--beforeAfter-left, calc(#{$-padding-h} * .5));
background: var(--treeEntry--beforeAfter-color, #{$border-color});
content: '';
}
&:before {
top: var(--treeEntry--before-top, 0);
width: 1px;
height: 100%;
}
&:after {
display: var(--treeEntry--after-display, initial);
top: calc(.5em + #{$-padding-v});
width: calc(#{$-padding-h} * .5);
height: 1px;
}
&:last-of-type:before {
height: calc(.5em + #{$-padding-v});
}
}
// bounce effect
@keyframes catchAttention {
0%, 20%, 40%, 60%, 80%, 100% {
@@ -2,12 +2,12 @@
<templates id="template" xml:space="preserve">
<t t-name="web.Legacy.SearchPanel" owl="1">
<div class="o_search_panel" t-att-class="props.className" t-ref="legacySearchPanel">
<div class="o_search_panel flex-grow-0 flex-shrink-0 border-end pe-2 pb-5 ps-4 h-100 bg-white overflow-auto" t-att-class="props.className" t-ref="legacySearchPanel">
<section t-foreach="model.get('sections', s => !s.empty)" t-as="section" t-key="section.id"
t-attf-class="o_search_panel_section o_search_panel_{{ section.type }}"
>
<header class="o_search_panel_section_header text-uppercase o_cursor_default">
<i t-attf-class="fa {{ section.icon }} o_search_panel_section_icon {{section.type === 'category' &amp;&amp; !section.color ? 'text-odoo' : ''}} me-2"
<header class="o_search_panel_section_header pt-4 pb-2 text-uppercase o_cursor_default">
<i t-attf-class="fa {{ section.icon }} o_search_panel_section_icon me-2 {{!section.color &amp;&amp; section.type == 'filter' ? 'text-warning' : !section.color ? 'text-odoo': ''}}"
t-att-style="section.color and ('color: ' + section.color)"
/>
<b t-esc="section.description"/>
@@ -20,14 +20,16 @@
<t t-set="values" t-value="section.rootIds"/>
</t>
<t t-elif="section.groups">
<li t-foreach="section.sortedGroupIds" t-as="groupId" t-key="groupId"
class="o_search_panel_filter_group list-group-item border-0"
<li
t-foreach="section.sortedGroupIds" t-as="groupId" t-key="groupId"
class="o_search_panel_filter_group list-group-item p-0 border-0"
t-att-class="groupId_last? 'mb-0' : 'mb-3'"
>
<!-- TODO: this is a workaround for issue https://github.com/odoo/owl/issues/695 (remove when solved) -->
<t t-set="_section" t-value="section"/>
<t t-set="group" t-value="section.groups.get(groupId)"/>
<header class="o_search_panel_group_header">
<div class="custom-control form-check">
<header class="o_search_panel_group_header pb-1">
<div class="form-check w-100">
<!-- TODO: "indeterminate" could not be set in the template and had to be set in
JS manually. See https://github.com/odoo/owl/issues/713 (adapt when solved)
-->
@@ -36,12 +38,13 @@
t-attf-id="{{ section.id }}_input_{{ groupId }})"
t-on-click="() => this._toggleFilterGroup(section.id, group)"
/>
<label t-attf-for="{{ section.id }}_input_{{ groupId }})"
class="o_search_panel_label form-check-label"
<label
t-attf-for="{{ section.id }}_input_{{ groupId }})"
class="o_search_panel_label form-check-label d-flex align-items-center justify-content-between w-100 o_cursor_pointer"
t-att-class="{ o_with_counters: group.enableCounters }"
t-att-title="group.tooltip or false"
>
<span class="o_search_panel_label_title">
<span class="o_search_panel_label_title text-truncate">
<span t-if="group.hex_color" class="me-1" t-attf-style="color: {{ group.hex_color }};">●</span>
<t t-esc="group.name"/>
</span>
@@ -51,6 +54,7 @@
<ul class="list-group d-block">
<t t-call="web.Legacy.SearchPanel.FiltersGroup">
<t t-set="values" t-value="group.values"/>
<t t-set="isChildList" t-value="true"/>
<!-- TODO: this is a workaround for issue https://github.com/odoo/owl/issues/695 (remove when solved) -->
<t t-set="section" t-value="_section"/>
</t>
@@ -76,28 +80,46 @@
<t t-name="web.Legacy.SearchPanel.Category" owl="1">
<t t-foreach="values" t-as="valueId" t-key="valueId">
<t t-set="value" t-value="section.values.get(valueId)"/>
<li class="o_search_panel_category_value list-group-item border-0">
<header class="list-group-item-action"
t-att-class="{ active: state.active[section.id] === valueId }"
<li class="o_search_panel_category_value list-group-item py-1 o_cursor_pointer border-0"
t-att-class="isChildList ? 'o_treeEntry ps-4 pe-0' : 'ps-0 pe-2'"
>
<header
class="list-group-item list-group-item-action d-flex align-items-center p-0 border-0"
t-att-class="{'active text-900 font-weight-bold': state.active[section.id] === valueId}"
t-on-click="() => this._toggleCategory(section, value)"
>
<label class="o_search_panel_label mb0" t-att-class="{ o_with_counters: section.enableCounters }">
<div class="o_toggle_fold">
<i t-if="value.childrenIds.length"
t-attf-class="fa fa-caret-{{ state.expanded[section.id][valueId] ? 'down' : 'right' }}"
<div
class="o_search_panel_label d-flex align-items-center overflow-hidden w-100 o_cursor_pointer mb-0"
t-att-class="{'o_with_counters': section.enableCounters }"
>
<button class="o_toggle_fold btn p-0 flex-shrink-0 text-center">
<i
t-if="value.childrenIds.length"
class="fa"
t-att-class="{
'fa-caret-down' : state.expanded[section.id][valueId],
'fa-caret-right ms-1': !state.expanded[section.id][valueId]
}"
/>
</button>
<span
class="o_search_panel_label_title text-truncate"
t-att-class="{'font-weight-bold' : value.bold}"
t-esc="value.display_name"
/>
</div>
<b t-if="value.bold" class="o_search_panel_label_title" t-esc="value.display_name"/>
<span t-else="" class="o_search_panel_label_title" t-esc="value.display_name"/>
</label>
<span t-if="section.enableCounters and value.__count gt 0"
class="o_search_panel_counter text-muted ms-2 small"
</div>
<small t-if="section.enableCounters and value.__count gt 0"
class="o_search_panel_counter text-muted mx-2 font-weight-bold"
t-esc="value.__count"
/>
</header>
<ul t-if="value.childrenIds.length and state.expanded[section.id][valueId]" class="list-group d-block">
<ul t-if="value.childrenIds.length and state.expanded[section.id][valueId]"
class="list-group d-block"
>
<t t-call="web.Legacy.SearchPanel.Category">
<t t-set="values" t-value="value.childrenIds"/>
<t t-set="isChildList" t-value="true"/>
</t>
</ul>
</li>
@@ -106,7 +128,8 @@
<t t-name="web.Legacy.SearchPanel.FiltersGroup" owl="1">
<li t-foreach="[...values.keys()]" t-as="valueId" t-key="valueId"
class="o_search_panel_filter_value list-group-item border-0"
class="o_search_panel_filter_value list-group-item p-0 mb-1 border-0 o_cursor_pointer"
t-att-class="{ 'ps-2' : isChildList }"
>
<t t-set="value" t-value="values.get(valueId)"/>
<div class="custom-control form-check w-100">
@@ -116,12 +139,12 @@
class="form-check-input"
t-on-click="ev => this._toggleFilterValue(section.id, valueId, ev)"
/>
<label class="o_search_panel_label form-check-label"
<label class="o_search_panel_label form-check-label d-flex align-items-center justify-content-between w-100 o_cursor_pointer"
t-attf-for="{{ section.id }}_input_{{ valueId }}"
t-att-title="(group and group.tooltip) or false">
<span class="o_search_panel_label_title" t-esc="value.display_name"/>
<span class="o_search_panel_label_title text-truncate" t-esc="value.display_name"/>
<span t-if="section.enableCounters and value.__count gt 0"
class="o_search_panel_counter text-muted ms-2 small"
class="o_search_panel_counter text-muted mx-2 small"
t-esc="value.__count"
/>
</label>
@@ -1,11 +1,4 @@
// ------- View with SearchPanel -------
$o-searchpanel-active-bg: rgba(108, 193, 237, 0.3);
$o-searchpanel-p: $o-horizontal-padding;
$o-searchpanel-p-small: $o-horizontal-padding*0.5;
$o-searchpanel-p-tiny: $o-searchpanel-p-small*0.5;
$o-searchpanel-category-default-color: $o-brand-primary;
$o-searchpanel-filter-default-color: #D59244;
.o_component_with_search_panel,
.o_controller_with_searchpanel {
@@ -25,123 +18,13 @@ $o-searchpanel-filter-default-color: #D59244;
}
}
.o_search_panel {
flex: 0 0 220px;
overflow: auto;
height: 100%;
padding: $o-searchpanel-p-small $o-searchpanel-p-small $o-searchpanel-p*2 $o-searchpanel-p;
border-right: 1px solid $gray-300;
background-color: white;
// ------- SearchPanel -------
.o_search_panel_filter .o_search_panel_section_icon {
color: $o-searchpanel-filter-default-color;
}
.o_search_panel {
width: var(--SearchPanel-width, #{$o-search-panel-width});
font-size: var(--SearchPanel-fontSize, #{$o-search-panel-font-size});
.o_toggle_fold {
text-align: center;
width: 1.5rem;
}
.o_search_panel_label {
align-items: center;
cursor: pointer;
display: flex;
justify-content: space-between;
user-select: none;
width: 100%;
}
.o_search_panel_section_header {
padding: $o-searchpanel-p-small 0;
}
.list-group {
padding-bottom: $o-searchpanel-p-tiny;
}
.list-group-item {
padding: 0;
.list-group-item {
padding: 0 0 0 map-get($gutters, 2);
margin-bottom: $o-searchpanel-p-tiny*0.5;
}
.o_search_panel_label_title {
color: $headings-color;
width: 100%;
@include o-text-overflow;
}
header.active {
background-color: $o-searchpanel-active-bg;
}
}
.o_search_panel_category_value {
cursor: pointer;
header {
align-items: center;
display: flex;
justify-content: space-between;
padding: 4px 6px 4px 0px;
}
.o_search_panel_label.o_with_counters {
overflow: hidden;
}
.o_search_panel_category_value {
margin-bottom: 0;
padding-left: $o-searchpanel-p;
position: relative;
&:before,
&:after {
content: '';
background: $gray-500;
margin-left: 4px;
@include o-position-absolute(0, $left: $o-searchpanel-p-tiny);
width: 1px;
height: 100%;
}
&:after {
top: 12px;
width: 8px;
height: 1px;
}
&:last-child:before {
height: 12px;
}
}
}
.o_search_panel_group_header .custom-control {
width: 100%;
}
.o_search_panel_filter_value,
.o_search_panel_filter_group {
cursor: pointer;
padding-bottom: $o-searchpanel-p-small;
.o_search_panel_label,
.o_search_panel_label_title {
padding-right: 6px;
}
}
.o_search_panel_filter_group {
header {
display: flex;
}
.o_search_panel_label.o_with_counters {
justify-content: flex-start;
}
width: map-get($spacers, 4);
}
}
@@ -0,0 +1,5 @@
// = Search Panel Variables
// ============================================================================
$o-search-panel-width: 200px;
$o-search-panel-font-size: 1em;
@@ -2,12 +2,12 @@
<templates id="template" xml:space="preserve">
<t t-name="web.SearchPanel" owl="1">
<div class="o_search_panel" t-att-class="env.searchModel.searchPanelInfo.className" t-ref="root">
<div class="o_search_panel flex-grow-0 flex-shrink-0 border-end pe-2 pb-5 ps-4 h-100 bg-white overflow-auto" t-att-class="env.searchModel.searchPanelInfo.className" t-ref="root">
<section t-foreach="sections" t-as="section" t-key="section.id"
t-attf-class="o_search_panel_section o_search_panel_{{ section.type }}"
>
<header class="o_search_panel_section_header text-uppercase o_cursor_default">
<i t-attf-class="fa {{ section.icon }} o_search_panel_section_icon {{section.type === 'category' &amp;&amp; !section.color ? 'text-odoo' : ''}} me-2"
<header class="o_search_panel_section_header pt-4 pb-2 text-uppercase o_cursor_default">
<i t-attf-class="fa {{ section.icon }} o_search_panel_section_icon {{!section.color &amp;&amp; section.type == 'filter' ? 'text-warning' : !section.color ? 'text-odoo': ''}} me-2"
t-att-style="section.color and ('color: ' + section.color)"
/>
<b t-esc="section.description"/>
@@ -20,14 +20,16 @@
<t t-set="values" t-value="section.rootIds"/>
</t>
<t t-elif="section.groups">
<li t-foreach="section.sortedGroupIds" t-as="groupId" t-key="groupId"
class="o_search_panel_filter_group list-group-item border-0"
<li
t-foreach="section.sortedGroupIds" t-as="groupId" t-key="groupId"
class="o_search_panel_filter_group list-group-item p-0 border-0"
t-att-class="groupId_last? 'mb-0' : 'mb-3'"
>
<!-- TODO: this is a workaround for issue https://github.com/odoo/owl/issues/695 (remove when solved) -->
<t t-set="_section" t-value="section"/>
<t t-set="group" t-value="section.groups.get(groupId)"/>
<header class="o_search_panel_group_header">
<div class="custom-control form-check">
<header class="o_search_panel_group_header pb-1">
<div class="form-check w-100">
<!-- TODO: "indeterminate" could not be set in the template and had to be set in
JS manually. See https://github.com/odoo/owl/issues/713 (adapt when solved)
-->
@@ -36,12 +38,13 @@
t-attf-id="{{ section.id }}_input_{{ groupId }})"
t-on-click="() => this.toggleFilterGroup(section.id, group)"
/>
<label t-attf-for="{{ section.id }}_input_{{ groupId }})"
class="o_search_panel_label form-check-label"
<label
t-attf-for="{{ section.id }}_input_{{ groupId }})"
class="o_search_panel_label form-check-label d-flex align-items-center justify-content-between w-100 o_cursor_pointer"
t-att-class="{ o_with_counters: group.enableCounters }"
t-att-title="group.tooltip or false"
>
<span class="o_search_panel_label_title">
<span class="o_search_panel_label_title text-truncate">
<span t-if="group.hex_color" class="me-1" t-attf-style="color: {{ group.hex_color }};">●</span>
<t t-esc="group.name"/>
</span>
@@ -51,6 +54,7 @@
<ul class="list-group d-block">
<t t-call="{{ constructor.subTemplates.filtersGroup }}">
<t t-set="values" t-value="group.values"/>
<t t-set="isChildList" t-value="true"/>
<!-- TODO: this is a workaround for issue https://github.com/odoo/owl/issues/695 (remove when solved) -->
<t t-set="section" t-value="_section"/>
</t>
@@ -76,28 +80,45 @@
<t t-name="web.SearchPanel.Category" owl="1">
<t t-foreach="values" t-as="valueId" t-key="valueId">
<t t-set="value" t-value="section.values.get(valueId)"/>
<li class="o_search_panel_category_value list-group-item border-0">
<header class="list-group-item-action"
t-att-class="{ active: state.active[section.id] === valueId }"
<li class="o_search_panel_category_value list-group-item py-1 o_cursor_pointer border-0"
t-att-class="isChildList ? 'o_treeEntry ps-4 pr-0' : 'ps-0 pe-2'"
>
<header
class="list-group-item list-group-item-action d-flex align-items-center p-0 border-0"
t-att-class="{'active text-900 font-weight-bold': state.active[section.id] === valueId}"
t-on-click="() => this.toggleCategory(section, value)"
>
<label class="o_search_panel_label mb0" t-att-class="{ o_with_counters: section.enableCounters }">
<div class="o_toggle_fold">
<i t-if="value.childrenIds.length"
t-attf-class="fa fa-caret-{{ state.expanded[section.id][valueId] ? 'down' : 'right' }}"
<div
class="o_search_panel_label d-flex align-items-center overflow-hidden w-100 o_cursor_pointer mb-0"
t-att-class="{'o_with_counters': section.enableCounters }"
>
<button class="o_toggle_fold btn p-0 flex-shrink-0 text-center">
<i
t-if="value.childrenIds.length"
class="fa"
t-att-class="{
'fa-caret-down' : state.expanded[section.id][valueId],
'fa-caret-right ms-1': !state.expanded[section.id][valueId]
}"
/>
</button>
<span
class="o_search_panel_label_title text-truncate"
t-att-class="{'font-weight-bold' : value.bold}"
t-esc="value.display_name"
/>
</div>
<b t-if="value.bold" class="o_search_panel_label_title" t-esc="value.display_name"/>
<span t-else="" class="o_search_panel_label_title" t-esc="value.display_name"/>
</label>
<span t-if="section.enableCounters and value.__count gt 0"
class="o_search_panel_counter text-muted ms-2 small"
</div>
<small t-if="section.enableCounters and value.__count gt 0"
class="o_search_panel_counter text-muted mx-2 font-weight-bold"
t-esc="value.__count"
/>
</header>
<ul t-if="value.childrenIds.length and state.expanded[section.id][valueId]" class="list-group d-block">
<ul t-if="value.childrenIds.length and state.expanded[section.id][valueId]"
class="list-group d-block"
>
<t t-call="{{ constructor.subTemplates.category }}">
<t t-set="values" t-value="value.childrenIds"/>
<t t-set="isChildList" t-value="true"/>
</t>
</ul>
</li>
@@ -106,7 +127,8 @@
<t t-name="web.SearchPanel.FiltersGroup" owl="1">
<li t-foreach="[...values.keys()]" t-as="valueId" t-key="valueId"
class="o_search_panel_filter_value list-group-item border-0"
class="o_search_panel_filter_value list-group-item p-0 mb-1 border-0 o_cursor_pointer"
t-att-class="{ 'ps-2' : isChildList }"
>
<t t-set="value" t-value="values.get(valueId)"/>
<div class="custom-control form-check w-100">
@@ -116,12 +138,12 @@
class="form-check-input"
t-on-click="ev => this.toggleFilterValue(section.id, valueId, ev)"
/>
<label class="o_search_panel_label form-check-label"
<label class="o_search_panel_label form-check-label d-flex align-items-center justify-content-between w-100 o_cursor_pointer"
t-attf-for="{{ section.id }}_input_{{ valueId }}"
t-att-title="(group and group.tooltip) or false">
<span class="o_search_panel_label_title" t-esc="value.display_name"/>
<span class="o_search_panel_label_title text-truncate" t-esc="value.display_name"/>
<span t-if="section.enableCounters and value.__count gt 0"
class="o_search_panel_counter text-muted ms-2 small"
class="o_search_panel_counter text-muted mx-2 small"
t-esc="value.__count"
/>
</label>
@@ -378,7 +378,7 @@ QUnit.module('Views', {
});
assert.deepEqual(
[...kanban.el.querySelectorAll('.o_search_panel_category_value header.active label')].map(
[...kanban.el.querySelectorAll('.o_search_panel_category_value header.active .o_search_panel_label')].map(
el => el.innerText
),
['All', 'GHI']
@@ -387,7 +387,7 @@ QUnit.module('Views', {
// select 'ABC' in the category 'state'
await testUtils.dom.click(kanban.el.querySelectorAll('.o_search_panel_category_value header')[4]);
assert.deepEqual(
[...kanban.el.querySelectorAll('.o_search_panel_category_value header.active label')].map(
[...kanban.el.querySelectorAll('.o_search_panel_category_value header.active .o_search_panel_label')].map(
el => el.innerText
),
['All', 'ABC']
@@ -4125,7 +4125,7 @@ QUnit.module('Views', {
'search_panel_select_range',
]);
assert.deepEqual(
[...kanban.el.querySelectorAll('.o_search_panel_category_value header label')].map(el => el.innerText),
[...kanban.el.querySelectorAll('.o_search_panel_category_value header .o_search_panel_label')].map(el => el.innerText),
['All', 'ABC', 'DEF', 'GHI']
);
@@ -4134,7 +4134,7 @@ QUnit.module('Views', {
assert.verifySteps([]);
assert.deepEqual(
[...kanban.el.querySelectorAll('.o_search_panel_category_value header label')].map(el => el.innerText),
[...kanban.el.querySelectorAll('.o_search_panel_category_value header .o_search_panel_label')].map(el => el.innerText),
['All', 'ABC', 'DEF', 'GHI']
);
@@ -4146,7 +4146,7 @@ QUnit.module('Views', {
'search_panel_select_range',
]);
assert.deepEqual(
[...kanban.el.querySelectorAll('.o_search_panel_category_value header label')].map(el => el.innerText),
[...kanban.el.querySelectorAll('.o_search_panel_category_value header .o_search_panel_label')].map(el => el.innerText),
['All', 'DEF']
);
@@ -507,7 +507,7 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(
[
...target.querySelectorAll(
".o_search_panel_category_value header.active label"
".o_search_panel_category_value header.active .o_search_panel_label"
),
].map((el) => el.innerText),
["All", "GHI"]
@@ -520,7 +520,7 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(
[
...target.querySelectorAll(
".o_search_panel_category_value header.active label"
".o_search_panel_category_value header.active .o_search_panel_label"
),
].map((el) => el.innerText),
["All", "ABC"]