From 0209661c704c89b06caf015e1a576e90de278e6f Mon Sep 17 00:00:00 2001 From: eyunhua <876482803@qq.com> Date: Thu, 24 Nov 2022 14:49:04 +0800 Subject: [PATCH] feat(cascader): add cascader component,and update styles. #296 --- demo/cases/Cascader.vue | 389 ++++++++++++++++++++++++++++++ demo/cases/index.js | 6 + src/components/Cascader.js | 33 +++ src/components/CascaderPane.js | 28 +++ src/components/cascader-pane.less | 258 ++++++++++++++++++++ src/components/cascader.less | 81 +++++++ src/mixins.less | 45 ++++ src/trigger.less | 200 +++++++++++++++ 8 files changed, 1040 insertions(+) create mode 100644 demo/cases/Cascader.vue create mode 100644 src/components/Cascader.js create mode 100644 src/components/CascaderPane.js create mode 100644 src/components/cascader-pane.less create mode 100644 src/components/cascader.less create mode 100644 src/trigger.less diff --git a/demo/cases/Cascader.vue b/demo/cases/Cascader.vue new file mode 100644 index 00000000..5865fef3 --- /dev/null +++ b/demo/cases/Cascader.vue @@ -0,0 +1,389 @@ + + + + + + diff --git a/demo/cases/index.js b/demo/cases/index.js index 77ab0ead..70187880 100644 --- a/demo/cases/index.js +++ b/demo/cases/index.js @@ -61,6 +61,7 @@ import Lightbox from './Lightbox'; import VDragSort from './VDragSort'; import Plugins from './Plugins'; import Message from './Message'; +import Cascader from './Cascader'; export default [ { @@ -144,6 +145,11 @@ export default [ name: 'Carousel', component: Carousel }, + { + path: '/cascader', + name: 'Cascader', + component: Cascader + }, { path: '/checkbox', name: 'Checkbox', diff --git a/src/components/Cascader.js b/src/components/Cascader.js new file mode 100644 index 00000000..c651f23a --- /dev/null +++ b/src/components/Cascader.js @@ -0,0 +1,33 @@ +import 'veui-theme-blue-icons/plain-down'; +import 'veui-theme-blue-icons/disc-x'; +import ui from 'veui/managers/ui'; + +const TAG_SIZE_MAP = { + xs: 's', + s: 's', + m: 's', + l: 'm' +}; + +ui.defaults( + { + icons: { + expand: 'plain-down', + collapse: 'plain-down', + clear: 'disc-x', + separator: 'plain-down' + }, + ui: { + size: { + values: ['xs', 's', 'm', 'l'], + inherit: true, + default: 'm' + } + }, + parts: { + clear: 'icon aux', + tag: ({size}) => TAG_SIZE_MAP[size] || size + } + }, + 'cascader' +); diff --git a/src/components/CascaderPane.js b/src/components/CascaderPane.js new file mode 100644 index 00000000..499df421 --- /dev/null +++ b/src/components/CascaderPane.js @@ -0,0 +1,28 @@ +import 'veui-theme-blue-icons/plain-right'; +import ui from 'veui/managers/ui'; + +const CHECKBOX_SIZE_MAP = { + xs: 's', + s: 's', + m: 'm', + l: 'm' +}; + +ui.defaults( + { + icons: { + expandable: 'plain-right' + }, + ui: { + size: { + values: ['xs', 's', 'm', 'l'], + inherit: true, + default: 'm' + } + }, + parts: { + checkbox: ({size}) => CHECKBOX_SIZE_MAP[size] || size + } + }, + 'cascaderpane' +); diff --git a/src/components/cascader-pane.less b/src/components/cascader-pane.less new file mode 100644 index 00000000..2db3cfbf --- /dev/null +++ b/src/components/cascader-pane.less @@ -0,0 +1,258 @@ +@import "../lib.less"; +@import (reference) "../dropdown.less"; + +.@{veui-prefix}-cascader-pane { + @min-panel-width: 30 * @veui-padding-unit; + @option-padding-x: 3 * @veui-padding-unit; + + display: flex; + + &-tree { + .reset-list-style(); + margin: 0; + } + + &-tree:not(.@{veui-prefix}-abstract-tree-item-group) { + will-change: height; + .veui-transition(height); + } + + &-leave-active, + &-enter-active { + will-change: margin-left; + .veui-transition(margin-left); + } + + &-leave-to, + &-enter { + margin-left: -@min-panel-width; + } + + &-custom-width &-leave-to, + &-custom-width &-enter { + margin-left: calc( + -1 * var(--veui-cascader-pane-custom-width, @min-panel-width) + ); + } + + &-leave-active { + z-index: 0 !important; + } + + &-column { + .padding(@veui-padding-unit, _); + position: relative; + overflow: auto; + // max-height 用来控制搜索时最大高度,此时高度会 reset 成 auto + @sizes: xs, s, m, l; + .veui-dropdown-menu-max-height( + @sizes, + var(--veui-dropdown-max-display-items, 8), + true + ); + } + + &-column-wrap:not(:first-child) { + border-left: 1px solid @veui-border-color-normal; + } + + // 下拉第一层没有缩进,其他层缩进 + &-column > &-tree { + padding-left: 0; + box-sizing: content-box; + flex: auto; + overflow-x: hidden; + position: relative; + } + + &-tree &-tree { + padding-left: 3 * @veui-padding-unit; + } + + &-column-wrap { + min-width: @min-panel-width; + background-color: @veui-option-background-color; + } + + // 内联平分 + &-inline &-column-wrap { + flex: 1 0; + } + + &-custom-width &-column-wrap { + flex: none; + + &:last-child { + flex: 1 0; + } + } + + // option + &-group-label, + &-option { + &:extend(._veui-dropdown-option all); + .padding(_, @option-padding-x); + // reset dropdown option background, use tree item background + background-color: transparent; + + &:hover { + color: @veui-font-color-normal; + background-color: transparent; + } + position: relative; + } + + &-option-label { + display: flex; + align-items: center; + } + + // tree hover style + &-option-wrap { + height: @veui-option-height-m; + outline: none; + + &[ui~="xs"] { + height: @veui-option-height-xs; + } + + &[ui~="s"] { + height: @veui-option-height-s; + } + + &[ui~="l"] { + height: @veui-option-height-l; + } + + &:not(&-disabled) { + &::before { + content: ""; + position: absolute; + left: 0; + height: inherit; + width: 200%; // hack around Safari's bug + .veui-transition(background-color); + } + + &:hover { + &::before { + background-color: @veui-background-color-normal-hover; + } + } + + &[data-focus-visible-added] { + .@{veui-prefix}-cascader-pane-option-label { + color: @veui-option-font-color-focus; + } + + &::before { + background-color: @veui-option-background-color-focus; + } + } + + // :active applies to all elements + &:active { + .@{veui-prefix}-cascader-pane-option-label { + color: @veui-option-font-color-active; + } + + &::before { + background-color: @veui-background-color-normal-hover; + } + } + } + + &:not(&-disabled)&-expanded { + &::before { + font-weight: @veui-option-font-weight-selected-parent; + background-color: @veui-option-background-color-selected-parent; + } + + &:active::before { + background-color: @veui-option-background-color-active; + } + } + + &-disabled { + cursor: not-allowed; + background-color: @veui-option-background-color; + + .@{veui-prefix}-cascader-pane-option { + cursor: not-allowed; + color: @veui-option-font-color-disabled; + } + } + } + + &:not(&-multiple) &-option-wrap-selected:not(&-option-wrap-disabled) { + .@{veui-prefix}-cascader-pane-option-label { + color: @veui-option-font-color-selected; + } + } + + &-group-label { + display: flex; + align-items: center; + max-width: @veui-option-max-width-m; + font-size: @veui-option-font-size-m; + + .padding(@veui-option-group-label-padding-y, @option-padding-x); + + .@{veui-prefix}-cascader-pane-option-label { + flex-grow: 1; + } + + .@{veui-prefix}-cascader-pane-expandable { + color: @veui-option-icon-color-aux; + .veui-icon-size(@veui-option-icon-size-aux); + margin-left: @veui-option-label-spacing-after; + font-size: @veui-font-size-s; + } + } + + &-column-before, + &-column-after { + padding: 2 * @veui-padding-unit 3 * @veui-padding-unit; + color: @veui-font-color-aux; + } + + &-column-before { + margin-top: -@veui-option-padding-s; + } + + &-column-after { + margin-bottom: -@veui-option-padding-s; + position: sticky; + top: 100%; + } + + &[ui~="xs"] { + .@{veui-prefix}-cascader-pane-option-wrap { + height: @veui-option-height-xs; + .@{veui-prefix}-cascader-pane-option-label { + font-size: @veui-option-font-size-xs; + max-width: @veui-option-max-width-xs; + } + } + } + + &[ui~="s"] { + .@{veui-prefix}-cascader-pane-option-wrap { + height: @veui-option-height-s; + .@{veui-prefix}-cascader-pane-option-label { + font-size: @veui-option-font-size-s; + max-width: @veui-option-max-width-s; + } + } + } + + &[ui~="l"] { + .@{veui-prefix}-cascader-pane-option-wrap { + height: @veui-option-height-l; + .@{veui-prefix}-cascader-pane-option-label { + font-size: @veui-option-font-size-l; + max-width: @veui-option-max-width-l; + } + } + } +} diff --git a/src/components/cascader.less b/src/components/cascader.less new file mode 100644 index 00000000..293d1cca --- /dev/null +++ b/src/components/cascader.less @@ -0,0 +1,81 @@ +@import "../lib.less"; +@import (reference) "../dropdown.less"; +@import (reference) "../trigger.less"; + +.@{veui-prefix}-cascader { + display: inline-flex; + outline: none; + .veui-stub(); + + &[data-focus-visible-added] .@{veui-prefix}-input { + .veui-focus-ring(@veui-border-color-focus, @veui-shadow-focus); + + &.@{veui-prefix}-invalid { + .veui-focus-ring( + @veui-border-color-error, + @veui-shadow-error-focus + ); + } + } + + .@{veui-prefix}-trigger { + &:extend(._veui-select-trigger all); + } + + &-pane-wrap { + &:extend(._veui-dropdown-overlay all); + overflow-y: auto; + background-color: @veui-option-background-color; + outline: none; + } + + &-overlay &-pane-wrap { + max-height: none; // 覆盖 overlay 对下拉的高度限制 + } + + // 特殊处理:将全选伪造成第一层 + &-select-all + &-pane + &-pane-column:first-child + > &-pane-tree + > li + > &-pane-tree { + padding-left: 0; + } + + &-pane-option { + .veui-search-result(); + } + + &-search-result { + .@{veui-prefix}-cascader-pane-column-wrap { + width: 100%; + } + .@{veui-prefix}-cascader-pane-column { + height: auto; + } + + &-item-separator { + transform: rotate(-90deg); + } + } + + // 切换到搜索干掉错误的过渡 + &-search-result &-pane-tree { + transition-duration: 0s; + } + + &-before, + &-after { + padding: 2 * @veui-padding-unit 3 * @veui-padding-unit; + color: @veui-font-color-aux; + } + + &-before { + border-bottom: 1px solid @veui-option-group-separator-color; + } + + &-after { + border-top: 1px solid @veui-option-group-separator-color; + } +} diff --git a/src/mixins.less b/src/mixins.less index 55e19b29..0dd77b13 100644 --- a/src/mixins.less +++ b/src/mixins.less @@ -92,3 +92,48 @@ .veui-icon-size(@size, @fixed-width) when (@fixed-width = true) { .size(@size); } + +.veui-search-result() { + .@{veui-prefix}-search-result-item-matched { + color: @veui-option-font-color-highlighted; + background: transparent; + } + .@{veui-prefix}-search-result-item-separator { + margin: 0 2 * @veui-spacing-unit; // TODO + } +} + +.veui-max-height(@item-height, @max-count, @fixed-height: false, @padding-y: @veui-padding-unit) { + & when (isnumber(@max-count)) { + max-height: veui-sum(@item-height * (@max-count + 0.5), @padding-y); + & when (@fixed-height = true) { + height: veui-sum(@item-height * (@max-count + 0.5), @padding-y); + } + } + + & when not (isnumber(@max-count)) { + max-height: veui-sum(@item-height * 8.5, @padding-y); // ie 降级到 8.5 + max-height: calc(@item-height * (@max-count + 0.5) + @padding-y); + & when (@fixed-height = true) { + height: veui-sum(@item-height * 8.5, @padding-y); // ie 降级到 8.5 + height: calc(@item-height * (@max-count + 0.5) + @padding-y); + } + } +} + +.veui-dropdown-menu-max-height(@sizes, @max-count: 8, @fixed-height: false, @padding-y: @veui-padding-unit, @option-height: "veui-option-height") { + .loop(@index: 1) when (@index <= length(@sizes)) { + @size: extract(@sizes, @index); + @height: "@{option-height}-@{size}"; + + &[ui~="@{size}"] { + .veui-max-height(@@height, @max-count, @fixed-height, @padding-y); + } + + .loop(@index + 1); + } + + .loop(); + + overflow-y: auto; +} diff --git a/src/trigger.less b/src/trigger.less new file mode 100644 index 00000000..bf6f6b7b --- /dev/null +++ b/src/trigger.less @@ -0,0 +1,200 @@ +._veui-select-trigger { + @veui-select-padding-y: @veui-padding-unit; + @adjusted-select-padding-y: veui-sum(@veui-select-padding-y, -1px); + @trigger: ~"@{veui-prefix}-trigger"; + + @veui-select-tag-height-m: 24px; + + cursor: pointer; + + &-icon { + display: flex; + position: relative; + } + + &-placeholder { + color: #999; + } + + &-count { + margin-right: @veui-padding-unit; + } + + &-toggle { + .veui-icon-size(@veui-icon-size-aux); + .veui-transition(transform, color, opacity); + color: @veui-icon-color-aux; + } + + &.@{veui-prefix}-readonly .@{trigger}-toggle, + &.@{veui-prefix}-disabled .@{trigger}-toggle { + color: @veui-icon-color-aux-disabled; + } + + &:not(.@{veui-prefix}-readonly):not(.@{veui-prefix}-disabled):hover + .@{trigger}-clear + + .@{trigger}-toggle, + .@{veui-prefix}-focus .@{trigger}-clear + .@{trigger}-toggle, + &-clear[data-focus-visible-added] + .@{trigger}-toggle { + opacity: 0; + } + + &-expanded .@{trigger}-toggle { + transform: rotateZ(180deg); + } + + &-clear { + .absolute(50%, 50%, _, _); + transform: translate(50%, -50%); + opacity: 0; + z-index: 1; + + .@{veui-prefix}-icon, + .veui-icon { + .veui-icon-size(@veui-icon-size-normal); + } + } + + &:not(.@{veui-prefix}-readonly):not(.@{veui-prefix}-disabled):hover + .@{trigger}-clear, + .@{veui-prefix}-focus .@{trigger}-clear, + .@{trigger}-clear[data-focus-visible-added] { + opacity: 1; + font-size: @veui-font-size-m; + color: @veui-font-color-disabled; + } + + .@{veui-prefix}-input-input { + display: none; + } + + &-searchable { + cursor: text; + .@{veui-prefix}-input-input { + display: block; + } + } + + &:not(.@{trigger}-empty):not(.@{trigger}-expanded):not(.@{veui-prefix}-focus) + .@{veui-prefix}-input-placeholder { + color: @veui-font-color-normal; + } + + &.@{veui-prefix}-readonly .@{veui-prefix}-input-placeholder { + color: @veui-font-color-disabled; + } + + &-label { + max-width: none; + .ellipsis(); + + .@{veui-prefix}-readonly .@{trigger}-label { + color: @veui-font-color-disabled; + } + + .@{veui-prefix}-disabled .@{trigger}-label { + color: @veui-font-color-disabled; + } + } + + &-custom-selected { + .ellipsis(); + } + + &-wrap.@{veui-prefix}-input { + display: block; + min-width: 100%; + height: auto; + overflow: hidden; + + .@{veui-prefix}-input-before { + display: block; + overflow: visible; + + .@{veui-prefix}-tag { + float: left; + margin-right: @veui-padding-unit; + margin-top: @adjusted-select-padding-y; + border: 0 none; + + &-remove { + color: @veui-color-gray-7; + } + } + + .@{veui-prefix}-trigger-placeholder { + float: left; + margin-top: @adjusted-select-padding-y; + } + } + + .@{veui-prefix}-input-input { + float: left; + margin-top: @adjusted-select-padding-y; + width: 0; + } + + .@{veui-prefix}-input-placeholder { + margin-top: @adjusted-select-padding-y; + } + + .@{veui-prefix}-input-after { + float: right; + margin: @adjusted-select-padding-y 0; + } + } + + .@{veui-prefix}-input-after { + color: @veui-font-color-aux; + } + + .@{veui-prefix}-count-overflow { + color: @veui-font-color-normal; + } + + &-custom-label { + flex: 0 1 auto; + .ellipsis(); + } + + &-searchable.@{trigger}-multiple { + &:not(.@{veui-prefix}-trigger-empty) .@{veui-prefix}-input-content { + display: block; + } + + .@{veui-prefix}-input-input { + width: 0; + max-width: 100%; + } + + &.@{veui-prefix}-trigger-expanded { + .@{veui-prefix}-input-input { + width: 10px; + } + } + } + + .make-metrics (@size) { + @height: ~"veui-select-tag-height-@{size}"; + + &.@{veui-prefix}-trigger.@{trigger}-wrap { + .@{veui-prefix}-trigger-placeholder { + line-height: @@height; + } + + .@{veui-prefix}-input-after { + height: @@height; + } + + .@{veui-prefix}-tag { + height: @@height; + } + + .@{veui-prefix}-input-input { + height: @@height; + } + } + } + + .make-metrics(m); +}