From f01d68f07578308f692070955daab490286d7f03 Mon Sep 17 00:00:00 2001 From: unfound <448217185@qq.com> Date: Fri, 6 Aug 2021 20:30:51 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20select=E7=BB=84=E4=BB=B6=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/select/demo/demo-basic.tsx | 15 +++++++++++++++ devui/select/demo/select-demo.tsx | 24 +++++++++++++++--------- devui/select/select.scss | 20 ++++++++++++++++++++ devui/select/select.tsx | 25 ++++++++++++++++++++----- 4 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 devui/select/demo/demo-basic.tsx create mode 100644 devui/select/select.scss diff --git a/devui/select/demo/demo-basic.tsx b/devui/select/demo/demo-basic.tsx new file mode 100644 index 00000000..eda65bf6 --- /dev/null +++ b/devui/select/demo/demo-basic.tsx @@ -0,0 +1,15 @@ +import { defineComponent } from 'vue' +import DSelect from '../select' + +export default defineComponent({ + name: 'DSelectDemo', + setup() { + return () => { + return ( + <> + + + ) + } + } +}) \ No newline at end of file diff --git a/devui/select/demo/select-demo.tsx b/devui/select/demo/select-demo.tsx index ef734f00..c292abb7 100644 --- a/devui/select/demo/select-demo.tsx +++ b/devui/select/demo/select-demo.tsx @@ -1,12 +1,18 @@ -import { defineComponent } from 'vue' +import { defineComponent } from 'vue'; +import { useDemo } from 'hooks/use-demo'; +import DemoBasic from './demo-basic'; +import DemoBasicCode from './demo-basic?raw'; export default defineComponent({ - name: 'd-select-demo', - props: { - }, - setup(props, ctx) { - return () => { - return
devui-select-demo
- } + name: 'DTextInputDemo', + render () { + return useDemo([ + { + id: 'demo-basic', + title: '基本用法', + code: DemoBasicCode, + content: + } + ]); } -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/devui/select/select.scss b/devui/select/select.scss new file mode 100644 index 00000000..e463f092 --- /dev/null +++ b/devui/select/select.scss @@ -0,0 +1,20 @@ +@import '../style/mixins/index'; +@import '../style/theme/color'; +@import '../style/theme/corner'; + +.devui-select { + position: relative; + width: 100%; +} + +.devui-select-dropdown { + position: absolute; + max-height: 200px; + overflow: auto; + top: 100%; + left: 0; + margin: 5px 0; + border-radius: 2px; + background: #ffffff; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2); +} diff --git a/devui/select/select.tsx b/devui/select/select.tsx index e011365d..5cb63169 100644 --- a/devui/select/select.tsx +++ b/devui/select/select.tsx @@ -1,13 +1,28 @@ +import './select.scss' import { defineComponent } from 'vue' export default defineComponent({ - name: 'd-select', - props: { - }, - setup(props, ctx) { + name: 'DSelect', + setup() { return () => { - return
devui-select
+ return ( +
+
+ +
+
+
    +
  • 试试
  • +
  • 试试
  • +
+
+
+ ) } } }) \ No newline at end of file -- Gitee From 90b5785d52e0efe6ea5b34fcffe29af60c327e29 Mon Sep 17 00:00:00 2001 From: "448217185@qq.com" Date: Sat, 7 Aug 2021 10:08:08 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/select/demo/demo-basic.tsx | 2 +- devui/select/{ => src}/select.scss | 7 ++++--- devui/select/{ => src}/select.tsx | 0 3 files changed, 5 insertions(+), 4 deletions(-) rename devui/select/{ => src}/select.scss (67%) rename devui/select/{ => src}/select.tsx (100%) diff --git a/devui/select/demo/demo-basic.tsx b/devui/select/demo/demo-basic.tsx index eda65bf6..71d29dcf 100644 --- a/devui/select/demo/demo-basic.tsx +++ b/devui/select/demo/demo-basic.tsx @@ -1,5 +1,5 @@ import { defineComponent } from 'vue' -import DSelect from '../select' +import DSelect from '../src/select' export default defineComponent({ name: 'DSelectDemo', diff --git a/devui/select/select.scss b/devui/select/src/select.scss similarity index 67% rename from devui/select/select.scss rename to devui/select/src/select.scss index e463f092..467ca155 100644 --- a/devui/select/select.scss +++ b/devui/select/src/select.scss @@ -1,6 +1,6 @@ -@import '../style/mixins/index'; -@import '../style/theme/color'; -@import '../style/theme/corner'; +@import '../../style/mixins/index'; +@import '../../style/theme/color'; +@import '../../style/theme/corner'; .devui-select { position: relative; @@ -10,6 +10,7 @@ .devui-select-dropdown { position: absolute; max-height: 200px; + width: calc(100% - 2px); overflow: auto; top: 100%; left: 0; diff --git a/devui/select/select.tsx b/devui/select/src/select.tsx similarity index 100% rename from devui/select/select.tsx rename to devui/select/src/select.tsx -- Gitee From 6dbe1dd770e192629a9eb3d25ed1302a7ed9cc79 Mon Sep 17 00:00:00 2001 From: unfound <448217185@qq.com> Date: Sat, 7 Aug 2021 20:27:31 +0800 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20select=E7=BB=84=E4=BB=B6=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/select/demo/demo-basic.tsx | 24 +++++++- devui/select/src/select.scss | 63 ++++++++++++++++++++- devui/select/src/select.tsx | 97 +++++++++++++++++++++++++------- devui/select/src/use-select.ts | 30 ++++++++++ 4 files changed, 192 insertions(+), 22 deletions(-) create mode 100644 devui/select/src/use-select.ts diff --git a/devui/select/demo/demo-basic.tsx b/devui/select/demo/demo-basic.tsx index 71d29dcf..485fa93a 100644 --- a/devui/select/demo/demo-basic.tsx +++ b/devui/select/demo/demo-basic.tsx @@ -1,13 +1,33 @@ -import { defineComponent } from 'vue' +import { defineComponent, Ref, ref, watch } from 'vue' import DSelect from '../src/select' export default defineComponent({ name: 'DSelectDemo', setup() { + function log (bool: boolean) { + console.log('toggleChange', bool) + } + + const value = ref(9) + watch(value, () => { + console.log('值改变了!', value.value) + }) + + const doUpdate = (valueRef: Ref, newVal: string | number) => { + valueRef.value = newVal + } + const updateProps = { + 'onUpdate:value': (newVal: string | number) => doUpdate(value, newVal) + } return () => { return ( <> - + ) } diff --git a/devui/select/src/select.scss b/devui/select/src/select.scss index 467ca155..3a84e422 100644 --- a/devui/select/src/select.scss +++ b/devui/select/src/select.scss @@ -9,7 +9,6 @@ .devui-select-dropdown { position: absolute; - max-height: 200px; width: calc(100% - 2px); overflow: auto; top: 100%; @@ -18,4 +17,66 @@ border-radius: 2px; background: #ffffff; box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2); + z-index: 999; +} + +.devui-select-dropdown-list { + max-height: 300px; + width: 100%; + overflow-y: auto; +} + +.devui-select-item { + font-size: var(--devui-font-size, 12px); + display: block; + min-height: 36px; + line-height: 1.5; + width: 100%; + padding: 10px; + clear: both; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + border: 0; + color: $devui-text; + + &:hover:not(.active) { + color: $devui-list-item-hover-text; + background-color: $devui-list-item-hover-bg; + } + + &.active { + color: $devui-list-item-active-text; + background-color: $devui-list-item-active-bg; + } +} + +.devui-scrollbar { + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-corner { + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + border-radius: 8px; + background-color: var(--devui-line, #adb0b8); + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } +} + +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.5s ease; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; } diff --git a/devui/select/src/select.tsx b/devui/select/src/select.tsx index 5cb63169..0e1cab3e 100644 --- a/devui/select/src/select.tsx +++ b/devui/select/src/select.tsx @@ -1,28 +1,87 @@ import './select.scss' -import { defineComponent } from 'vue' +import { defineComponent, ref, Transition, computed } from 'vue' +import { selectProps, SelectProps, OptionItem } from './use-select' export default defineComponent({ name: 'DSelect', - setup() { - return () => { - return ( -
-
- -
-
-
    -
  • 试试
  • -
  • 试试
  • + props: selectProps, + emits: ['toggleChange', 'valueChange', 'update:value'], + setup (props: SelectProps, ctx) { + const isOpen = ref(false) + function toggleChange (bool: boolean) { + isOpen.value = bool + ctx.emit('toggleChange', bool) + } + + const inputValue = computed(() => { + return props.value + '' + }) + function valueChange (item: OptionItem, index: number) { + const value = typeof item === 'object' ? item.value : item + ctx.emit('update:value', value) + ctx.emit('valueChange', item, index) + toggleChange(false) + } + + function getItemClassName (item: OptionItem) { + let classname = 'devui-select-item' + const value = typeof item === 'object' ? item.value : item + if (value === props.value) { + classname = 'devui-select-item active' + } + return classname + } + + return { + isOpen, + inputValue, + valueChange, + toggleChange, + getItemClassName, + ...props + } + }, + render () { + const { + options, + isOpen, + inputValue, + valueChange, + toggleChange, + getItemClassName + } = this + + return ( +
    +
    + toggleChange(true) } + /> +
    + +
    +
      + { + options.map((item, i) => ( +
    • { valueChange(item, i) } } + class={ getItemClassName(item) } + key={ i } + > + { typeof item === 'object' ? item.name : item } +
    • + )) + }
    -
    - ) - } + +
+ ) } }) \ No newline at end of file diff --git a/devui/select/src/use-select.ts b/devui/select/src/use-select.ts new file mode 100644 index 00000000..d5bb77e2 --- /dev/null +++ b/devui/select/src/use-select.ts @@ -0,0 +1,30 @@ +import { PropType, ExtractPropTypes } from 'vue' + +export interface OptionObjectItem { + name: string + value: string | number + [key: string]: any +} +export type OptionItem = number | string | OptionObjectItem +export type Options = Array + +export const selectProps = { + value: { + type: [String, Number] as PropType, + default: '' + }, + 'onUpdate:value': { + type: Function as PropType<(val: string | number) => void>, + default: undefined + }, + options: { + type: Array as PropType, + default: () => [] + }, + onToggleChange: { + type: Function as PropType<(bool: boolean) => void>, + default: undefined + } +} as const + +export type SelectProps = ExtractPropTypes -- Gitee From ac6301abb2a03cb3639d5f117e88bcd94148359d Mon Sep 17 00:00:00 2001 From: "448217185@qq.com" Date: Sun, 8 Aug 2021 20:47:43 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20select=E6=96=87=E6=A1=A3=E5=92=8C?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=EF=BC=8C=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?props=E7=9A=84=E8=A7=A3=E6=9E=84=E5=93=8D=E5=BA=94=E5=A4=B1?= =?UTF-8?q?=E6=95=88=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 1 + devui/select/__tests__/select.spec.ts | 114 ++++++++++++++++++++++++++ devui/select/src/select.scss | 101 ++++++++++++++++++++++- devui/select/src/select.tsx | 66 +++++++++++---- devui/select/src/use-select.ts | 20 ++++- devui/select/src/utils.ts | 16 ++++ devui/vue-devui.ts | 5 +- sites/.vitepress/config/sidebar.ts | 1 + sites/components/select/index.md | 92 +++++++++++++++++++++ 9 files changed, 394 insertions(+), 22 deletions(-) create mode 100644 devui/select/__tests__/select.spec.ts create mode 100644 devui/select/src/utils.ts create mode 100644 sites/components/select/index.md diff --git a/.eslintrc.js b/.eslintrc.js index 12abdbca..3da2f803 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,6 +25,7 @@ module.exports = { 'no-undef': 2, 'vue/max-attributes-per-line': 'off', 'vue/no-multiple-template-root': 'off', + 'vue/script-setup-uses-vars': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/member-delimiter-style': [ 'error', diff --git a/devui/select/__tests__/select.spec.ts b/devui/select/__tests__/select.spec.ts new file mode 100644 index 00000000..7557ddfb --- /dev/null +++ b/devui/select/__tests__/select.spec.ts @@ -0,0 +1,114 @@ +import { mount } from '@vue/test-utils' +import { ref, reactive, nextTick } from 'vue' +import DSelect from '../src/select' + +describe('select', () => { + it('select render work', async () => { + const value = ref(1) + const options = reactive([1, 2, 'string']) + const wrapper = mount({ + components: { DSelect }, + template: ``, + setup() { + return { + value, + options + } + } + }) + const container = wrapper.find('.devui-select') + let dropdown = wrapper.find('.devui-select-dropdown') + let listItems = wrapper.findAll('.devui-select-item') + const input = wrapper.find('.devui-select-input') + const arrow = wrapper.find('.devui-select-arrow') + + expect(container.exists()).toBeTruthy() + expect(dropdown.isVisible()).toBeFalsy() + expect(arrow.isVisible()).toBeTruthy() + expect(listItems.length).toBe(3) + expect(listItems[0].classes()).toContain('active') + expect(input.attributes('placeholder')).toBe('这是默认选择框') + expect(input.element.value).toBe('1') + + await input.trigger('click') + await nextTick() + // isVisible不会自动更新需要重新获取 + dropdown = wrapper.find('.devui-select-dropdown') + expect(dropdown.isVisible()).toBeTruthy() + expect(container.classes()).toContain('devui-select-open') + + await listItems[2].trigger('click') + await nextTick() + + // isVisible不会自动更新需要重新获取 + dropdown = wrapper.find('.devui-select-dropdown') + expect(value.value).toBe('string') + expect(dropdown.isVisible()).toBeFalsy() + expect(input.element.value).toBe('string') + // class不会自动更新需要重新获取 + listItems = wrapper.findAll('.devui-select-item') + expect(listItems[2].classes()).toContain('active') + }) + + it('select size and overview work', async () => { + const wrapper = mount(DSelect, { + props: { + size: 'sm', + overview: 'underlined' + } + }) + + let container = wrapper.find('.devui-select') + expect(container.classes()).toContain('devui-select-sm') + expect(container.classes()).toContain('devui-select-underlined') + + await wrapper.setProps({ + size: 'lg', + overview: 'border' + }) + + container = wrapper.find('.devui-select') + expect(container.classes()).toContain('devui-select-lg') + expect(container.classes()).not.toContain('devui-select-underlined') + }) + + it('select events work', async () => { + const value = ref(2) + const options = reactive([6, 2, 'test']) + const toggleChange = jest.fn() + const valueChange = jest.fn() + const wrapper = mount({ + components: { DSelect }, + template: ` + + `, + setup() { + return { + value, + options, + toggleChange, + valueChange + } + } + }) + + const input = wrapper.find('.devui-select-input') + await input.trigger('click') + + expect(toggleChange).toBeCalledTimes(1) + expect(valueChange).toBeCalledTimes(0) + expect(value.value).toBe(2) + + const listItems = wrapper.findAll('.devui-select-item') + await listItems[2].trigger('click') + + expect(toggleChange).toBeCalledTimes(2) + expect(valueChange).toBeCalledTimes(1) + expect(value.value).toBe('test') + }) +}) diff --git a/devui/select/src/select.scss b/devui/select/src/select.scss index 3a84e422..6f0ad29a 100644 --- a/devui/select/src/select.scss +++ b/devui/select/src/select.scss @@ -1,12 +1,103 @@ @import '../../style/mixins/index'; @import '../../style/theme/color'; @import '../../style/theme/corner'; +@import '@devui-design/icons/icomoon/devui-icon.css'; + +$border-change-time: 300ms; +$border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); + +@mixin border-transition { + transition: border-color $border-change-time $border-change-function; +} .devui-select { position: relative; width: 100%; } +.devui-select-underlined { + border-bottom: 1px solid $devui-form-control-line; + @include border-transition(); + + &:not([disabled]):not(.disabled) { + &:hover { + border-color: $devui-form-control-line-hover; + } + + &.devui-select-open { + border-color: $devui-form-control-line-active; + } + } + + .devui-select-input { + border: none; + } +} + +.devui-select-open { + .devui-select-arrow { + transform: rotate3d(0, 0, 1, 180deg); + } +} + +.devui-select-selection { + position: relative; +} + +.devui-select-input { + cursor: pointer; + width: 100%; + height: 28px; + padding: 4px 28px 4px 10px; + color: $devui-text; + vertical-align: middle; + border: 1px solid $devui-form-control-line; + border-radius: $devui-border-radius; + outline: none; + background-color: $devui-base-bg; + @include border-transition(); + + &:not([disabled]):not(.disabled) { + &:hover { + border-color: $devui-form-control-line-hover; + } + + &:focus { + border-color: $devui-form-control-line-active; + } + } + + &[disabled], + &.disabled { + &:hover { + cursor: not-allowed; + background-color: $devui-disabled-bg; + border-color: $devui-disabled-line; + color: $devui-disabled-text; + } + } + + &.devui-select-input-lg { + height: 44px; + } + + &.devui-select-input-sm { + height: 24px; + } +} + +.devui-select-arrow { + position: absolute; + right: 0; + height: 100%; + width: 28px; + display: inline-flex; + justify-content: center; + align-items: center; + transform: rotate3d(0, 0, 1, 0deg); + transition: transform 0.25s ease-out; +} + .devui-select-dropdown { position: absolute; width: calc(100% - 2px); @@ -24,6 +115,8 @@ max-height: 300px; width: 100%; overflow-y: auto; + padding: 0; + margin: 0; } .devui-select-item { @@ -39,6 +132,7 @@ text-overflow: ellipsis; border: 0; color: $devui-text; + cursor: pointer; &:hover:not(.active) { color: $devui-list-item-hover-text; @@ -73,10 +167,15 @@ .fade-enter-active, .fade-leave-active { - transition: opacity 0.5s ease; + transform: scale3d(0, 1, 0, 0.9999) translate3d(0, 1, 0, 0); + transform-origin: 0 0%; + opacity: 1; + transition: transform, opacity 0.25s ease-out; } .fade-enter-from, .fade-leave-to { + transform: scale3d(0, 1, 0, 0) translate3d(0, 1, 0, -4px); opacity: 0; + transition: transform, opacity 0.25s ease-in; } diff --git a/devui/select/src/select.tsx b/devui/select/src/select.tsx index 0e1cab3e..47c91b01 100644 --- a/devui/select/src/select.tsx +++ b/devui/select/src/select.tsx @@ -1,12 +1,13 @@ - import './select.scss' -import { defineComponent, ref, Transition, computed } from 'vue' +import { defineComponent, ref, Transition, toRefs } from 'vue' import { selectProps, SelectProps, OptionItem } from './use-select' +import DIcon from '../../icon/src/icon' +import { className } from './utils' export default defineComponent({ name: 'DSelect', props: selectProps, - emits: ['toggleChange', 'valueChange', 'update:value'], + emits: ['toggleChange', 'valueChange', 'update:modelValue'], setup (props: SelectProps, ctx) { const isOpen = ref(false) function toggleChange (bool: boolean) { @@ -14,23 +15,35 @@ export default defineComponent({ ctx.emit('toggleChange', bool) } - const inputValue = computed(() => { - return props.value + '' - }) + const inputValue = ref(props.modelValue + '') + initInputValue() + + function initInputValue () { + props.options.forEach(item => { + if (typeof item === 'object' && item.value === props.modelValue) { + inputValue.value = item.name + } + }) + } + function valueChange (item: OptionItem, index: number) { const value = typeof item === 'object' ? item.value : item - ctx.emit('update:value', value) + inputValue.value = getInputValue(item) + ctx.emit('update:modelValue', value) ctx.emit('valueChange', item, index) toggleChange(false) } function getItemClassName (item: OptionItem) { - let classname = 'devui-select-item' const value = typeof item === 'object' ? item.value : item - if (value === props.value) { - classname = 'devui-select-item active' - } - return classname + return className('devui-select-item', { + active: value === props.modelValue + }) + } + + function getInputValue (item: OptionItem) { + const value = typeof item === 'object' ? item.name : item + return value + '' } return { @@ -39,7 +52,7 @@ export default defineComponent({ valueChange, toggleChange, getItemClassName, - ...props + ...toRefs(props) } }, render () { @@ -47,22 +60,41 @@ export default defineComponent({ options, isOpen, inputValue, + size, + placeholder, + overview, valueChange, toggleChange, getItemClassName } = this + const selectClassName = className('devui-select', { + 'devui-select-open': isOpen, + 'devui-select-lg': size === 'lg', + 'devui-select-sm': size === 'sm', + 'devui-select-underlined': overview === 'underlined' + }) + + const inputClassName = className('devui-select-input', { + 'devui-select-input-lg': size === 'lg', + 'devui-select-input-sm': size === 'sm', + }) + return ( -
+
toggleChange(true) } + onClick={ () => toggleChange(!isOpen) } + onBlur={ () => toggleChange(false) } /> + + +
diff --git a/devui/select/src/use-select.ts b/devui/select/src/use-select.ts index d5bb77e2..fb0891e8 100644 --- a/devui/select/src/use-select.ts +++ b/devui/select/src/use-select.ts @@ -9,11 +9,11 @@ export type OptionItem = number | string | OptionObjectItem export type Options = Array export const selectProps = { - value: { + modelValue: { type: [String, Number] as PropType, default: '' }, - 'onUpdate:value': { + 'onUpdate:modelValue': { type: Function as PropType<(val: string | number) => void>, default: undefined }, @@ -21,9 +21,25 @@ export const selectProps = { type: Array as PropType, default: () => [] }, + size: { + type: String as PropType<'sm' | 'md' | 'lg'>, + default: 'md' + }, + overview: { + type: String as PropType<'border' | 'underlined'>, + default: 'border' + }, + placeholder: { + type: String, + default: '请选择' + }, onToggleChange: { type: Function as PropType<(bool: boolean) => void>, default: undefined + }, + onValueChange: { + type: Function as PropType<(item: OptionItem, index: number) => void>, + default: undefined } } as const diff --git a/devui/select/src/utils.ts b/devui/select/src/utils.ts new file mode 100644 index 00000000..225b8323 --- /dev/null +++ b/devui/select/src/utils.ts @@ -0,0 +1,16 @@ +/** + * 动态获取class字符串 + * @param classStr 是一个字符串,固定的class名 + * @param classOpt 是一个对象,key表示class名,value为布尔值,true则添加,否则不添加 + * @returns 最终的class字符串 + */ +export function className (classStr: string, classOpt?: { [key: string]: boolean; }): string { + let classname = classStr + if (typeof classOpt === 'object') { + Object.keys(classOpt).forEach(key => { + classOpt[key] && (classname += ` ${key}`) + }) + } + + return classname +} diff --git a/devui/vue-devui.ts b/devui/vue-devui.ts index 5dfe708b..ffe2b6f0 100644 --- a/devui/vue-devui.ts +++ b/devui/vue-devui.ts @@ -14,6 +14,7 @@ import Alert from './alert/alert'; // 数据录入 import Checkbox from './checkbox/src/checkbox'; import Radio from './radio/src/radio'; +import Select from './select/src/select' import Switch from './switch/src/switch'; import TagsInput from './tags-input/src/tags-input'; import TextInput from './text-input/src/text-input'; @@ -26,7 +27,7 @@ function install(app: App) { Button, Icon, Panel, Tabs, Alert, - Checkbox, Radio, Switch, TagsInput, TextInput, + Checkbox, Radio, Select, Switch, TagsInput, TextInput, Avatar, ]; packages.forEach((item:any) => { @@ -42,7 +43,7 @@ export { Button, Icon, Panel, Tabs, Alert, - Checkbox, Radio, Switch, TagsInput, TextInput, + Checkbox, Radio, Select, Switch, TagsInput, TextInput, Avatar, }; diff --git a/sites/.vitepress/config/sidebar.ts b/sites/.vitepress/config/sidebar.ts index 5dc1723d..a1f3c6a2 100644 --- a/sites/.vitepress/config/sidebar.ts +++ b/sites/.vitepress/config/sidebar.ts @@ -26,6 +26,7 @@ const sidebar = { children: [ { text: 'Checkbox 复选框', link: '/components/checkbox/' }, { text: 'Radio 单选框', link: '/components/radio/' }, + { text: 'Select 下拉选择框', link: '/components/select/' }, { text: 'Switch 开关', link: '/components/switch/' }, { text: 'TagsInput 标签输入', link: '/components/tags-input/' }, { text: 'TextInput 文本框', link: '/components/text-input/' }, diff --git a/sites/components/select/index.md b/sites/components/select/index.md new file mode 100644 index 00000000..70602349 --- /dev/null +++ b/sites/components/select/index.md @@ -0,0 +1,92 @@ +# Select 下拉选择框 + +用于从列表中选择单个或者多个数据 + +### 何时使用 + +需要从列表中选择单个或者多个数据 + +### 基本用法 + +#### Large + +
+ +
+
+ +#### Middle + +
+ +
+
+ +#### Small + +
+ +
+
+ +#### Underlined + +
+ +
+
+ +```html + + + + + + + +``` + + -- Gitee From 50bcb26848e7fed87db3b3d2f47cb6edb7674469 Mon Sep 17 00:00:00 2001 From: unfound <448217185@qq.com> Date: Mon, 9 Aug 2021 20:36:29 +0800 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/select/__tests__/select.spec.ts | 134 +++++++++++++------------- devui/select/src/select.scss | 36 ++++--- devui/select/src/select.tsx | 120 +++++++++++------------ devui/select/src/use-select.ts | 80 +++++++-------- devui/select/src/utils.ts | 15 +-- sites/components/select/index.md | 4 - 6 files changed, 198 insertions(+), 191 deletions(-) diff --git a/devui/select/__tests__/select.spec.ts b/devui/select/__tests__/select.spec.ts index 7557ddfb..d7fcaa9b 100644 --- a/devui/select/__tests__/select.spec.ts +++ b/devui/select/__tests__/select.spec.ts @@ -1,82 +1,82 @@ -import { mount } from '@vue/test-utils' -import { ref, reactive, nextTick } from 'vue' -import DSelect from '../src/select' +import { mount } from '@vue/test-utils'; +import { ref, reactive, nextTick } from 'vue'; +import DSelect from '../src/select'; describe('select', () => { it('select render work', async () => { - const value = ref(1) - const options = reactive([1, 2, 'string']) + const value = ref(1); + const options = reactive([1, 2, 'string']); const wrapper = mount({ components: { DSelect }, template: ``, setup() { return { value, - options - } - } - }) - const container = wrapper.find('.devui-select') - let dropdown = wrapper.find('.devui-select-dropdown') - let listItems = wrapper.findAll('.devui-select-item') - const input = wrapper.find('.devui-select-input') - const arrow = wrapper.find('.devui-select-arrow') + options, + }; + }, + }); + const container = wrapper.find('.devui-select'); + let dropdown = wrapper.find('.devui-select-dropdown'); + let listItems = wrapper.findAll('.devui-select-item'); + const input = wrapper.find('.devui-select-input'); + const arrow = wrapper.find('.devui-select-arrow'); - expect(container.exists()).toBeTruthy() - expect(dropdown.isVisible()).toBeFalsy() - expect(arrow.isVisible()).toBeTruthy() - expect(listItems.length).toBe(3) - expect(listItems[0].classes()).toContain('active') - expect(input.attributes('placeholder')).toBe('这是默认选择框') - expect(input.element.value).toBe('1') + expect(container.exists()).toBeTruthy(); + expect(dropdown.isVisible()).toBeFalsy(); + expect(arrow.isVisible()).toBeTruthy(); + expect(listItems.length).toBe(3); + expect(listItems[0].classes()).toContain('active'); + expect(input.attributes('placeholder')).toBe('这是默认选择框'); + expect(input.element.value).toBe('1'); - await input.trigger('click') - await nextTick() + await input.trigger('click'); + await nextTick(); // isVisible不会自动更新需要重新获取 - dropdown = wrapper.find('.devui-select-dropdown') - expect(dropdown.isVisible()).toBeTruthy() - expect(container.classes()).toContain('devui-select-open') + dropdown = wrapper.find('.devui-select-dropdown'); + expect(dropdown.isVisible()).toBeTruthy(); + expect(container.classes()).toContain('devui-select-open'); - await listItems[2].trigger('click') - await nextTick() + await listItems[2].trigger('click'); + await nextTick(); // isVisible不会自动更新需要重新获取 - dropdown = wrapper.find('.devui-select-dropdown') - expect(value.value).toBe('string') - expect(dropdown.isVisible()).toBeFalsy() - expect(input.element.value).toBe('string') + dropdown = wrapper.find('.devui-select-dropdown'); + expect(value.value).toBe('string'); + expect(dropdown.isVisible()).toBeFalsy(); + expect(input.element.value).toBe('string'); // class不会自动更新需要重新获取 - listItems = wrapper.findAll('.devui-select-item') - expect(listItems[2].classes()).toContain('active') - }) + listItems = wrapper.findAll('.devui-select-item'); + expect(listItems[2].classes()).toContain('active'); + }); it('select size and overview work', async () => { const wrapper = mount(DSelect, { props: { size: 'sm', - overview: 'underlined' - } - }) + overview: 'underlined', + }, + }); - let container = wrapper.find('.devui-select') - expect(container.classes()).toContain('devui-select-sm') - expect(container.classes()).toContain('devui-select-underlined') + let container = wrapper.find('.devui-select'); + expect(container.classes()).toContain('devui-select-sm'); + expect(container.classes()).toContain('devui-select-underlined'); await wrapper.setProps({ size: 'lg', - overview: 'border' - }) + overview: 'border', + }); - container = wrapper.find('.devui-select') - expect(container.classes()).toContain('devui-select-lg') - expect(container.classes()).not.toContain('devui-select-underlined') - }) + container = wrapper.find('.devui-select'); + expect(container.classes()).toContain('devui-select-lg'); + expect(container.classes()).not.toContain('devui-select-underlined'); + }); it('select events work', async () => { - const value = ref(2) - const options = reactive([6, 2, 'test']) - const toggleChange = jest.fn() - const valueChange = jest.fn() + const value = ref(2); + const options = reactive([6, 2, 'test']); + const toggleChange = jest.fn(); + const valueChange = jest.fn(); const wrapper = mount({ components: { DSelect }, template: ` @@ -92,23 +92,23 @@ describe('select', () => { value, options, toggleChange, - valueChange - } - } - }) + valueChange, + }; + }, + }); - const input = wrapper.find('.devui-select-input') - await input.trigger('click') + const input = wrapper.find('.devui-select-input'); + await input.trigger('click'); - expect(toggleChange).toBeCalledTimes(1) - expect(valueChange).toBeCalledTimes(0) - expect(value.value).toBe(2) + expect(toggleChange).toBeCalledTimes(1); + expect(valueChange).toBeCalledTimes(0); + expect(value.value).toBe(2); - const listItems = wrapper.findAll('.devui-select-item') - await listItems[2].trigger('click') + const listItems = wrapper.findAll('.devui-select-item'); + await listItems[2].trigger('click'); - expect(toggleChange).toBeCalledTimes(2) - expect(valueChange).toBeCalledTimes(1) - expect(value.value).toBe('test') - }) -}) + expect(toggleChange).toBeCalledTimes(2); + expect(valueChange).toBeCalledTimes(1); + expect(value.value).toBe('test'); + }); +}); diff --git a/devui/select/src/select.scss b/devui/select/src/select.scss index 6f0ad29a..79a248e3 100644 --- a/devui/select/src/select.scss +++ b/devui/select/src/select.scss @@ -5,6 +5,14 @@ $border-change-time: 300ms; $border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); +$select-input-height-sm: 24px; +$select-input-height-md: 28px; +$select-input-height-lg: 44px; +$select-arrow-width: 28px; +$transition-base-time: 0.25s; +$select-dropdown-max-height: 300px; +$select-item-font-size: var(--devui-font-size, 12px); +$select-item-min-height: 36px; @mixin border-transition { transition: border-color $border-change-time $border-change-function; @@ -47,8 +55,8 @@ $border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); .devui-select-input { cursor: pointer; width: 100%; - height: 28px; - padding: 4px 28px 4px 10px; + height: $select-input-height-md; + padding: 4px $select-arrow-width 4px 10px; color: $devui-text; vertical-align: middle; border: 1px solid $devui-form-control-line; @@ -78,11 +86,11 @@ $border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); } &.devui-select-input-lg { - height: 44px; + height: $select-input-height-lg; } &.devui-select-input-sm { - height: 24px; + height: $select-input-height-sm; } } @@ -90,12 +98,12 @@ $border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); position: absolute; right: 0; height: 100%; - width: 28px; + width: $select-arrow-width; display: inline-flex; justify-content: center; align-items: center; transform: rotate3d(0, 0, 1, 0deg); - transition: transform 0.25s ease-out; + transition: transform $transition-base-time ease-out; } .devui-select-dropdown { @@ -105,14 +113,14 @@ $border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); top: 100%; left: 0; margin: 5px 0; - border-radius: 2px; - background: #ffffff; - box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2); + border-radius: $devui-border-radius; + background: $devui-base-bg; + box-shadow: 0 2px 5px 0 $devui-shadow; z-index: 999; } .devui-select-dropdown-list { - max-height: 300px; + max-height: $select-dropdown-max-height; width: 100%; overflow-y: auto; padding: 0; @@ -120,9 +128,9 @@ $border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); } .devui-select-item { - font-size: var(--devui-font-size, 12px); + font-size: $select-item-font-size; display: block; - min-height: 36px; + min-height: $select-item-min-height; line-height: 1.5; width: 100%; padding: 10px; @@ -170,12 +178,12 @@ $border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); transform: scale3d(0, 1, 0, 0.9999) translate3d(0, 1, 0, 0); transform-origin: 0 0%; opacity: 1; - transition: transform, opacity 0.25s ease-out; + transition: transform, opacity $transition-base-time ease-out; } .fade-enter-from, .fade-leave-to { transform: scale3d(0, 1, 0, 0) translate3d(0, 1, 0, -4px); opacity: 0; - transition: transform, opacity 0.25s ease-in; + transition: transform, opacity $transition-base-time ease-in; } diff --git a/devui/select/src/select.tsx b/devui/select/src/select.tsx index 47c91b01..513761f2 100644 --- a/devui/select/src/select.tsx +++ b/devui/select/src/select.tsx @@ -1,49 +1,49 @@ -import './select.scss' -import { defineComponent, ref, Transition, toRefs } from 'vue' -import { selectProps, SelectProps, OptionItem } from './use-select' -import DIcon from '../../icon/src/icon' -import { className } from './utils' +import './select.scss'; +import { defineComponent, ref, Transition, toRefs } from 'vue'; +import { selectProps, SelectProps, OptionItem } from './use-select'; +import DIcon from '../../icon/src/icon'; +import { className } from './utils'; export default defineComponent({ name: 'DSelect', props: selectProps, emits: ['toggleChange', 'valueChange', 'update:modelValue'], - setup (props: SelectProps, ctx) { - const isOpen = ref(false) - function toggleChange (bool: boolean) { - isOpen.value = bool - ctx.emit('toggleChange', bool) + setup(props: SelectProps, ctx) { + const isOpen = ref(false); + function toggleChange(bool: boolean) { + isOpen.value = bool; + ctx.emit('toggleChange', bool); } - const inputValue = ref(props.modelValue + '') - initInputValue() + const inputValue = ref(props.modelValue + ''); + initInputValue(); - function initInputValue () { - props.options.forEach(item => { + function initInputValue() { + props.options.forEach((item) => { if (typeof item === 'object' && item.value === props.modelValue) { - inputValue.value = item.name + inputValue.value = item.name; } - }) + }); } - - function valueChange (item: OptionItem, index: number) { - const value = typeof item === 'object' ? item.value : item - inputValue.value = getInputValue(item) - ctx.emit('update:modelValue', value) - ctx.emit('valueChange', item, index) - toggleChange(false) + + function valueChange(item: OptionItem, index: number) { + const value = typeof item === 'object' ? item.value : item; + inputValue.value = getInputValue(item); + ctx.emit('update:modelValue', value); + ctx.emit('valueChange', item, index); + toggleChange(false); } - function getItemClassName (item: OptionItem) { - const value = typeof item === 'object' ? item.value : item + function getItemClassName(item: OptionItem) { + const value = typeof item === 'object' ? item.value : item; return className('devui-select-item', { - active: value === props.modelValue - }) + active: value === props.modelValue, + }); } - function getInputValue (item: OptionItem) { - const value = typeof item === 'object' ? item.name : item - return value + '' + function getInputValue(item: OptionItem) { + const value = typeof item === 'object' ? item.name : item; + return value + ''; } return { @@ -52,10 +52,10 @@ export default defineComponent({ valueChange, toggleChange, getItemClassName, - ...toRefs(props) - } + ...toRefs(props), + }; }, - render () { + render() { const { options, isOpen, @@ -65,55 +65,55 @@ export default defineComponent({ overview, valueChange, toggleChange, - getItemClassName - } = this + getItemClassName, + } = this; const selectClassName = className('devui-select', { 'devui-select-open': isOpen, 'devui-select-lg': size === 'lg', 'devui-select-sm': size === 'sm', - 'devui-select-underlined': overview === 'underlined' - }) + 'devui-select-underlined': overview === 'underlined', + }); const inputClassName = className('devui-select-input', { 'devui-select-input-lg': size === 'lg', 'devui-select-input-sm': size === 'sm', - }) + }); return ( -
+
toggleChange(!isOpen) } - onBlur={ () => toggleChange(false) } + onClick={() => toggleChange(!isOpen)} + onBlur={() => toggleChange(false)} /> - +
-
+
    - { - options.map((item, i) => ( -
  • { valueChange(item, i) } } - class={ getItemClassName(item) } - key={ i } - > - { typeof item === 'object' ? item.name : item } -
  • - )) - } + {options.map((item, i) => ( +
  • { + valueChange(item, i); + }} + class={getItemClassName(item)} + key={i} + > + {typeof item === 'object' ? item.name : item} +
  • + ))}
- ) - } -}) \ No newline at end of file + ); + }, +}); diff --git a/devui/select/src/use-select.ts b/devui/select/src/use-select.ts index fb0891e8..60b40ee1 100644 --- a/devui/select/src/use-select.ts +++ b/devui/select/src/use-select.ts @@ -1,46 +1,46 @@ -import { PropType, ExtractPropTypes } from 'vue' +import { PropType, ExtractPropTypes } from 'vue'; export interface OptionObjectItem { - name: string - value: string | number - [key: string]: any + name: string + value: string | number + [key: string]: any } -export type OptionItem = number | string | OptionObjectItem -export type Options = Array +export type OptionItem = number | string | OptionObjectItem; +export type Options = Array; export const selectProps = { - modelValue: { - type: [String, Number] as PropType, - default: '' - }, - 'onUpdate:modelValue': { - type: Function as PropType<(val: string | number) => void>, - default: undefined - }, - options: { - type: Array as PropType, - default: () => [] - }, - size: { - type: String as PropType<'sm' | 'md' | 'lg'>, - default: 'md' - }, - overview: { - type: String as PropType<'border' | 'underlined'>, - default: 'border' - }, - placeholder: { - type: String, - default: '请选择' - }, - onToggleChange: { - type: Function as PropType<(bool: boolean) => void>, - default: undefined - }, - onValueChange: { - type: Function as PropType<(item: OptionItem, index: number) => void>, - default: undefined - } -} as const + modelValue: { + type: [String, Number] as PropType, + default: '', + }, + 'onUpdate:modelValue': { + type: Function as PropType<(val: string | number) => void>, + default: undefined, + }, + options: { + type: Array as PropType, + default: () => [], + }, + size: { + type: String as PropType<'sm' | 'md' | 'lg'>, + default: 'md', + }, + overview: { + type: String as PropType<'border' | 'underlined'>, + default: 'border', + }, + placeholder: { + type: String, + default: '请选择', + }, + onToggleChange: { + type: Function as PropType<(bool: boolean) => void>, + default: undefined, + }, + onValueChange: { + type: Function as PropType<(item: OptionItem, index: number) => void>, + default: undefined, + }, +} as const; -export type SelectProps = ExtractPropTypes +export type SelectProps = ExtractPropTypes; diff --git a/devui/select/src/utils.ts b/devui/select/src/utils.ts index 225b8323..3092da77 100644 --- a/devui/select/src/utils.ts +++ b/devui/select/src/utils.ts @@ -4,13 +4,16 @@ * @param classOpt 是一个对象,key表示class名,value为布尔值,true则添加,否则不添加 * @returns 最终的class字符串 */ -export function className (classStr: string, classOpt?: { [key: string]: boolean; }): string { - let classname = classStr +export function className( + classStr: string, + classOpt?: { [key: string]: boolean; } +): string { + let classname = classStr; if (typeof classOpt === 'object') { - Object.keys(classOpt).forEach(key => { - classOpt[key] && (classname += ` ${key}`) - }) + Object.keys(classOpt).forEach((key) => { + classOpt[key] && (classname += ` ${key}`); + }); } - return classname + return classname; } diff --git a/sites/components/select/index.md b/sites/components/select/index.md index 70602349..4af9aadc 100644 --- a/sites/components/select/index.md +++ b/sites/components/select/index.md @@ -13,28 +13,24 @@

-
#### Middle

-
#### Small

-
#### Underlined

-
```html -- Gitee From ea5a64082f6a0547dcb96b28b9cc194632f7ef5d Mon Sep 17 00:00:00 2001 From: "448217185@qq.com" Date: Mon, 9 Aug 2021 22:51:46 +0800 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4=E6=97=A7?= =?UTF-8?q?=E7=9A=84=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/select/demo/demo-basic.tsx | 35 ----------- devui/select/demo/select-demo.tsx | 18 ------ devui/select/demo/select.route.ts | 15 ----- devui/select/doc/api-cn.md | 96 ------------------------------- devui/select/doc/api-en.md | 95 ------------------------------ 5 files changed, 259 deletions(-) delete mode 100644 devui/select/demo/demo-basic.tsx delete mode 100644 devui/select/demo/select-demo.tsx delete mode 100644 devui/select/demo/select.route.ts delete mode 100644 devui/select/doc/api-cn.md delete mode 100644 devui/select/doc/api-en.md diff --git a/devui/select/demo/demo-basic.tsx b/devui/select/demo/demo-basic.tsx deleted file mode 100644 index 485fa93a..00000000 --- a/devui/select/demo/demo-basic.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { defineComponent, Ref, ref, watch } from 'vue' -import DSelect from '../src/select' - -export default defineComponent({ - name: 'DSelectDemo', - setup() { - function log (bool: boolean) { - console.log('toggleChange', bool) - } - - const value = ref(9) - watch(value, () => { - console.log('值改变了!', value.value) - }) - - const doUpdate = (valueRef: Ref, newVal: string | number) => { - valueRef.value = newVal - } - const updateProps = { - 'onUpdate:value': (newVal: string | number) => doUpdate(value, newVal) - } - return () => { - return ( - <> - - - ) - } - } -}) \ No newline at end of file diff --git a/devui/select/demo/select-demo.tsx b/devui/select/demo/select-demo.tsx deleted file mode 100644 index c292abb7..00000000 --- a/devui/select/demo/select-demo.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { defineComponent } from 'vue'; -import { useDemo } from 'hooks/use-demo'; -import DemoBasic from './demo-basic'; -import DemoBasicCode from './demo-basic?raw'; - -export default defineComponent({ - name: 'DTextInputDemo', - render () { - return useDemo([ - { - id: 'demo-basic', - title: '基本用法', - code: DemoBasicCode, - content: - } - ]); - } -}); \ No newline at end of file diff --git a/devui/select/demo/select.route.ts b/devui/select/demo/select.route.ts deleted file mode 100644 index c6565f55..00000000 --- a/devui/select/demo/select.route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import SelectDemoComponent from './select-demo' -import DevUIApiComponent from '../../shared/devui-api/devui-api' - -import ApiCn from '../doc/api-cn.md' -import ApiEn from '../doc/api-en.md' -const routes = [ - { path: '', redirectTo: 'demo' }, - { path: 'demo', component: SelectDemoComponent}, - { path: 'api', component: DevUIApiComponent, meta: { - 'zh-cn': ApiCn, - 'en-us': ApiEn - }} -] - -export default routes diff --git a/devui/select/doc/api-cn.md b/devui/select/doc/api-cn.md deleted file mode 100644 index 7b6472a5..00000000 --- a/devui/select/doc/api-cn.md +++ /dev/null @@ -1,96 +0,0 @@ -# 如何使用 - -在 module 中引入: - -```typescript -import { SelectModule } from 'ng-devui/select'; -``` - -在页面中使用: - -```html - -``` - -## d-select - -### d-select 参数 - -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :----------------------------------------------: | :-------------------------------------------------: | :----------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -| options | `array` | [] | 可选, 和 searchFn 互斥,两者必须有且只有一个。下拉选项资源`string` `object` | [基本用法](demo#basic-usage) | -| isSearch | `boolean` | false | 可选,是否支持过滤搜索 | [使用对象](demo#object-filter) | -| scrollHight | `string` | '300px' | 可选,下拉菜单高度,建议使用 px 作为高度单位 | -| highlightItemClass | `string` | 'bg-grey' | 可选,下拉高亮 css | -| filterKey | `string` | -- | 当传入资源 options 类型为 object 时,必选,针对传入资源 options 的每项对应字段做过滤操作 | [使用对象](demo#object-filter) | -| multiple | `boolean` | false | 可选,是否支持多选 | [自定义搜索功能](demo#custom-search) | -| isSelectAll | `boolean` | false | 可选,是否显示全选 | [全选下拉选项](demo#select-all) | -| readonly | `boolean` | true | 可选,是否可以输入 | [标签化](demo#labelization) | -| size | `string` | '' | 可选,下拉选框尺寸,有三种选择`'lg'`,`''`,`'sm'` | [基本用法](demo#basic-usage) | -| disabled | `boolean` | false | 可选,是否禁用下拉框 | [禁用](demo#disabled) | -| placeholder | `string` | 'Please Input keywords' | 可选,输入框的 placeholder | [基本用法](demo#basic-usage) | -| searchPlaceholder | `string` | '' | 可选,搜索功能输入框的 placeholder | [自定义搜索功能](demo#custom-search) | -| searchFn | `function` | -- | 可选,搜索函数,当需要自定义下拉选择过滤规则时可以使用 | [自定义搜索功能](demo#custom-search) | -| valueParser | `function` | -- | 可选,决定选择框文字如何显示,默认显示 filterKey 字段或者本身的值 | -| formatter | `function` | -- | 可选,决定下拉框每项文字如何显示,默认显示 filterKey 字段或者本身的值 | -| direction | `'up'\|'down'\|'auto'` | '' | 可选,下拉选框尺寸,有三种选择`'up'`,`'down'`,`'auto'` | [禁用](demo#disabled) | -| overview | `string` | 'border' | 可选,决定选择框样式显示,默认有边框`'border'`,`'underlined'` | [基本用法](demo#basic-usage) | -| enableLazyLoad | `boolean` | false | 可选,是否支持数据懒加载,用于滚动到底部时动态请求数据 | [虚拟滚动 或 懒加载](demo#lazy-load-virtual-scroll) | -| extraConfig | `object` | N/A | 可选, 可输入配置项 参考示例 | [自定义模板](demo#select-template) | -| extraConfig.labelization | `object` | N/A | 可选, 标签化多选结果的配置项,参考示例 | [标签化](demo#labelization) | -| extraConfig.labelization.enable | `boolean` | false | 可选下的必填参数, 是否启用标签化,使用必须置为 true,参考示例 | [标签化](demo#labelization) | -| extraConfig.labelization.overflow | `string` | '' | 可选, 多个标签超出容器时候的预处理行为,值为`'normal' \| 'scroll-y' \| 'multiple-line' \| 'string'` 对应默认隐藏,纵向滚动、自动变多行和自定义类 | [标签化](demo#labelization) | -| extraConfig.labelization.containerMaxHeight | `string` | '1.8em' | 可选, 限制容器最高高度。 多行模式下默认不限制高度,单行模式下默认为 1.8em | -| ~~extraConfig.labelization.containnerMaxHeight~~ | `string` | '1.8em' | 已废弃, 限制容器最高高度。 多行模式下默认不限制高度,单行模式下默认为 1.8em, 请使用`extraConfig.labelization.containerMaxHeight` | -| extraConfig.labelization.labelMaxWidth | `string` | '100%' | 可选下, 限制标签宽度,默认值为行宽的 100% | -| extraConfig.selectedItemWithTemplate | `object` | N/A | 可选,在单选情况下,显示选项使用了 template 的情况下,顶部选中的内容是否也以 template 展示,参考示例 | [自定义模板](demo#select-template) | -| extraConfig.selectedItemWithTemplate.enable | `boolean` | -- | 可选下的必填参数, 是否启用选中项使用模板,使用必须置为 true,参考示例 | [自定义模板](demo#select-template) | -| optionDisabledKey | `string` | '' | 可选,禁用单个选项;当传入资源 options 类型为`Object`,比如设置为`'disabled'`,则当对象的 disabled 属性为 true 时,该选项将禁用;当设置为`''`时不禁用单个选项 | [禁用](demo#disabled) | -| optionImmutableKey | `string` | '' | 可选,禁用单个选项;当传入资源 options 类型为`Object`,比如设置为`'immutable'`,则当对象的 immutable 属性为 true 时,该选项将禁被禁止变更;当设置为`''`时不生效 | [禁用](demo#disabled) | -| noResultItemTemplate | `TemplateRef` | -- | 可选,没有匹配项的展示结果 | -| keepMultipleOrder | `string` | 'user-select' | 可选,`'user-select' \| 'origin'`,配置多选的时候是否维持原数组排序还是用户选择的顺序排序,默认是用户顺序 | [设置已选项顺序源数组顺序或选中顺序](demo#multi-keep-order) | -| customViewTemplate | `TemplateRef` | -- | 可选,支持自定义区域显示内容定制,可以使用 choose 来选择某项,choose 需要传两个必填参数,第一个为选择的选项,第二个为选项在列表的 index 值,event 参数选填,若不填请自行处理冒泡,详见 demo | [自定义区域](demo#custom-area) | -| customViewDirection | `'bottom' \| 'right'\| 'left'` | 'bottom' | 可选, customViewTemplate 所处的相对下拉列表的位置 | [自定义区域方向和选中](demo#custom-area-direction) | -| appendToBody | `boolean` | false | 可选, true 会被附加到 body | [附着到 Body 上](demo#append-to-body) | -| appendToBodyDirections | `Array` | `['rightDown', 'leftDown', 'rightUp', 'leftUp']` | 可选,方向数组优先采用数组里靠前的位置,AppendToBodyDirection 和 ConnectedPosition 请参考 dropdown | [自定义区域方向和选中](demo#custom-area-direction) | -| autoFocus | `boolean` | false | 可选,是否自动聚焦 | -| toggleOnFocus | `boolean` | false | 可选,是否 focus 自动展开下拉列表 | -| width | `number` | -- | 可选,搭配 appendToBody 使用,设置下拉宽度 | [自定义区域方向和选中](demo#custom-area-direction) | -| virtualScroll | `boolean` | false | 可选,是否虚拟滚动,大数据量场景试用 | [虚拟滚动 或 懒加载](demo#lazy-load-virtual-scroll) | -| allowClear | `boolean` | false | 可选, 配置是否允许清空选值,仅单选场景适用 | [允许清空值](demo#allow-clear-value) | -| inputItemTemplate | `TemplateRef` | -- | 可选,自定义模板,若传入,会忽略 ContentChild | | -| ~~~notAutoScroll~~~ | `boolean` | false | `待改名`~~~可选,自动聚焦的时候,自动滚动到 select 位置~~~ | -| templateItemSize | `number` | false | `待完善`可选,模板单条高度, appendToBody 必须为 true | -| loadingTemplateRef | `TemplateRef` | -- | 可选,自定义 loading 模板 | [虚拟滚动 或 懒加载](demo#lazy-load-virtual-scroll) | - -### d-select 事件 - -| 事件 | 类型 | 说明 | 跳转 Demo | -| :----------: | :-------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| valueChange | `EventEmitter\|any>` | 可选,输出函数,当选中某个选项项后,将会调用此函数,参数为当前选择项的值 | -| toggleChange | `EventEmitter` | 可选,输出函数,下拉打开关闭 toggle 事件 | [异步加载显示加载中](demo#async-loading) | -| loadMore | `EventEmitter<{instance: SelectComponent, event: ScrollEvent}>` | 懒加载触发事件,配合`enableLazyLoad`使用,使用`$event.instance.loadFinish()`结束本次加载, event 为懒加载监听的滚动事件,参考 dLazyLoad | [虚拟滚动 或 懒加载](demo#lazy-load-virtual-scroll) | - -注意: 使用 appendToBody 后需要在有滚动条的地方使用`cdkScrollable` - -```terminal -npm install @angular/cdk --save -``` - -```TypeScript -import { ScrollDispatchModule } from '@angular/cdk/scrolling'; - -@NgModule({ - imports: [ - // ... - ScrollDispatchModule, - // ... - ] -}) -``` - -```html -
- -
-``` diff --git a/devui/select/doc/api-en.md b/devui/select/doc/api-en.md deleted file mode 100644 index 4001d3fd..00000000 --- a/devui/select/doc/api-en.md +++ /dev/null @@ -1,95 +0,0 @@ -# How to use - -Import into module: - -```typescript -import { SelectModule } from 'ng-devui/select'; -``` - -In the page: - -```html - -``` - -## d-select - -### d-select Parameter - -| Parameter | Type | Default | Description | Jump to Demo | -| :----------------------------------------------: | :-------------------------------------------------: | :-------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| options | `array` | [] | Optional. This parameter and searchFn are mutually exclusive. Only one of them is required. Resource of the drop-down list box: `string` `object` | [Basic Usage](demo#basic-usage) | -| isSearch | `boolean` | false | Optional. indicating whether to support filtering search. | [Use object](demo#object-filter) | -| scrollHight | `string` | '300px' | Optional. Height of the drop-down list box. You are advised to use px as the height unit. | -| highlightItemClass | `string` | 'bg-grey' | Optional. The drop-down list box is highlighted. css | -| filterKey | `string` | -- | Optional. This parameter is required when the input resource options type is object. Filter the fields corresponding to the input resource options. | [Use object](demo#object-filter) | -| multiple | `boolean` | false | Optional. indicating whether to support multiple selections. | [Custom Search](demo#custom-search) | -| isSelectAll | `boolean` | false | Optional. Whether to display all options. | [Select All](demo#select-all) | -| readonly | `boolean` | true | Optional. Whether the value can be entered | [Tagged](demo#labelization) | -| size | `string` | '' | Optional. Size of the drop-down list box. The options are as follows: `'lg'`, `''`, and `'sm'` | [Basic Usage](demo#basic-usage) | -| disabled | `boolean` | false | Optional. indicating whether to disable the drop-down list box. | [Disable](demo#disabled) | -| placeholder | `string` | 'Please Input keywords' | Optional. This parameter specifies the placeholder of the input box. | [Basic Usage](demo#basic-usage) | -| searchPlaceholder | `string` | '' | Optional. placeholder in the search text box. | [Custom Search](demo#custom-search) | -| searchFn | `function` | -- | Optional. Search function. You can use this function when you need to customize filtering rules from the drop-down list box. | [Custom Search](demo#custom-search) | -| valueParser | `function` | -- | Optional. This parameter determines how to display the text in the selection box. By default, the filterKey field or its value is displayed. | -| formatter | `function` | -- | Optional. This parameter determines how to display each text in the drop-down list box. By default, the filterKey field or its value is displayed. | -| direction | `'up'\|'down'\|'auto'` | 'down' | Optional. The options are as follows: up, down, and auto. | [Disabled](demo#disabled) | -| overview | `string` | 'border' | Optional. This parameter specifies the selection box style. By default, the border is `'border'`,`'underlined'` | [Basic Usage](demo#basic-usage) | -| enableLazyLoad | `boolean` | false | Optional. Whether to support data lazy loading. It is used to dynamically request data when scrolling to the bottom. | [Virtual scrolling or lazy loading](demo#lazy-load-virtual-scroll) | -| extraConfig | `object` | N/A | Optional. You can enter configuration items. Example | [Customized template](demo#select-template) | -| extraConfig.labelization | `object` | N/A | Optional. It is a configuration item for labeling the multi-selection result. For details, see. | [Labeling](demo#labelization) | -| extraConfig.labelization.enable | `boolean` | false | Indicates whether to enable tagging. This parameter is mandatory under . The value must be true. For details, see the example | [Labeling](demo#labelization) | -| extraConfig.labelization.overflow | `string` | '' | Optional. Preprocessing behavior when multiple tags exceed the container. The value is `'normal'\| 'scroll-y' \| 'multiple-line' \| 'string'`, indicating that the tag is hidden by default, vertical scrolling, automatic multi-line, and custom class | [labeling](demo#labelization) | -| extraConfig.labelization.containerMaxHeight | `string` | '1.8em ' | Specifies the maximum height of the container. This parameter is optional. By default, the height is not limited in multi-line mode. In single-line mode, the height is 1.8em by default. | -| ~~extraConfig.labelization.containnerMaxHeight~~ | `string` | '1.8em' | `Deprecated`. specifies the maximum height of a container. By default, the height is not limited in multi-line mode. In single-line mode, the height is 1.8 em by default. Use `extraConfig.labelization.containerMaxHeight` | . | -| extraConfig.labelization.labelMaxWidth | `string` | '100% ' | Optional. Limit the label width. The default value is 100% of the row width. | -| extraConfig.selectedItemWithTemplate | `object` | N/A | Optional. When a single option is selected and the display option is set to template, check whether the selected content is displayed in the template format. For details, see the example. | [Customized template](demo#select-template) | -| extraConfig.selectedItemWithTemplate.enable | `boolean` | -- | This parameter is required under labelization. It specifies whether to enable the selected items to use the template. The value must be true. For details, see Example | [Customized Template](demo#select-template) | -| optionDisabledKey | `string` | '' | Optional. A single option is disabled. If the input resource options type is Object, for example, Disabled, and the disabled attribute of the object is true, this option is disabled. When this parameter is set to `''`, a single option is not disabled. | [Disabled](demo#disabled) | -| optionImmutableKey | `string` | '' | Optional. A single option is disabled. If the input resource option type is Object, for example, immutable, and the immutable attribute of the object is true, the option cannot be changed. This parameter does not take effect when it is set to `''`. | [Disabled](demo#disabled) | -| noResultItemTemplate | `TemplateRef` | -- | Optional. No matching result is displayed. | -| keepMultipleOrder | `string` | 'user-select' | Optional. `'user-select' \| 'origin'` indicates whether to maintain the original array or user-selected sequence when multiple selections are performed, the default value is the user order. | [Sets the selected order source array order or selection order](demo#multi-keep-order) | -| customViewTemplate | `TemplateRef` | -- | Optional. Content displayed in the customized area can be customized. You can choose to select an item. Two mandatory parameters need to be transferred. The first parameter is the selected option, the second parameter is the index value of the option in the list, and the event parameter is Optional. if this parameter is left empty, handle the pop-up. For details, see demo | [Custom Area](demo#custom-area) | -| customViewDirection | `'bottom' \|'right'\|'left'` | 'bottom' | customViewTemplate position in the relative drop-down list box | [Customizing Area Orientation and Selecting](demo#custom-area-direction) | -| appendToBody | `boolean` | false | Optional. true: The value is attached to the body. | [Attach to the body](demo#append-to-body) | -| appendToBodyDirections | `Array` | `['rightDown','leftDown','rightUp','leftUp']` | Optional. The first position in the array is preferred for the direction array, for details about AppendToBodyDirection and ConnectedPosition, see dropdown | [CCustomizing Area Orientation and Selecting](demo#custom-area-direction) | -| autoFocus | `boolean` | false | Optional. Whether to enable auto-focus. | -| toggleOnFocus | `boolean` | false | Optional. indicating whether to automatically expand the drop-down list box by focusing. | -| width | `number` | -- | Optional. This parameter is used with appendToBody to set the drop-down width. | [Customizing Area Orientation and Selecting](demo#custom-area-direction) | -| virtualScroll | `boolean` | false | Optional. Whether to use virtual scrolling. This parameter is used in scenarios with a large amount of data. | [Virtual scrolling or lazy loading](demo#lazy-load-virtual-scroll) | -| allowClear | `boolean` | false | Optional specifies whether to clear the selected value. This parameter applies only to single-choice scenarios. | [Allowed to clear value](demo#allow-clear-value) | -| inputItemTemplate | `TemplateRef` | -- | Optional. Customized template. If this parameter is transferred, ContentChild is ignored. | | -| ~~~notAutoScroll~~~ | `boolean` | false | `To be renamed`~~~ Optional. When autofocus is enabled, the system automatically scrolls to the select position.~~~ | -| templateItemSize | `number` | false | `To be improved` Optional. The height of a single template is required. appendToBody must be true. | -| loadingTemplateRef | `TemplateRef` | -- | Optional. Customized loading template | [Virtual scrolling or lazy loading](demo#lazy-load-virtual-scroll) | - -### d-select event - -| Event | Type | Description | Jump to Demo | -| :----------: | :-------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -| valueChange | `EventEmitter\|any>` | Optional. output function. This function is invoked after an option is selected. The parameter is the value of the current option. | -| toggleChange | `EventEmitter` | Optional. output function. It is optional. It is used to enable or disable the toggle event. | [Asynchronous loading indicates that the toggle event is being loaded](demo#async-loading) | -| loadMore | `EventEmitter<{instance: SelectComponent, event: ScrollEvent}>` | lazy loading trigger event. This event is used together with `enableLazyLoad` and `$event.instance.loadFinish()` is used to end the loading. event indicates the lazy loading listening scrolling event. For details, see dLazyLoad | [Virtual scrolling or lazy loading](demo#lazy-load-virtual-scroll) | . | - -Note: After appendToBody is used, use `cdkScrollable` where the scroll bar exists. - -```terminal -npm install @angular/cdk --save -``` - -```TypeScript -import {ScrollDispatchModule} from '@angular/cdk/scrolling'; -@NgModule({ -imports: [ -//... -ScrollDispatchModule, -//... -] -}) -``` - -```html -
- -
-``` -- Gitee From de48f0656c3030c73173adbeb80a38118ac31bee Mon Sep 17 00:00:00 2001 From: unfound <448217185@qq.com> Date: Tue, 10 Aug 2021 17:19:31 +0800 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DPR=E5=BB=BA?= =?UTF-8?q?=E8=AE=AE=EF=BC=9A=E5=88=A0=E9=99=A4icons=E7=9A=84=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E5=BC=95=E5=85=A5=EF=BC=9B=E5=B0=86=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E5=BC=95=E5=85=A5=E6=94=BE=E5=88=B0import=E6=9C=80=E4=B8=8B?= =?UTF-8?q?=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/select/src/select.scss | 1 - devui/select/src/select.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/devui/select/src/select.scss b/devui/select/src/select.scss index 79a248e3..f520b07d 100644 --- a/devui/select/src/select.scss +++ b/devui/select/src/select.scss @@ -1,7 +1,6 @@ @import '../../style/mixins/index'; @import '../../style/theme/color'; @import '../../style/theme/corner'; -@import '@devui-design/icons/icomoon/devui-icon.css'; $border-change-time: 300ms; $border-change-function: cubic-bezier(0.645, 0.045, 0.355, 1); diff --git a/devui/select/src/select.tsx b/devui/select/src/select.tsx index 513761f2..b164187c 100644 --- a/devui/select/src/select.tsx +++ b/devui/select/src/select.tsx @@ -1,8 +1,8 @@ -import './select.scss'; import { defineComponent, ref, Transition, toRefs } from 'vue'; import { selectProps, SelectProps, OptionItem } from './use-select'; import DIcon from '../../icon/src/icon'; import { className } from './utils'; +import './select.scss'; export default defineComponent({ name: 'DSelect', -- Gitee