forked from opentiny/tiny-vue
feat(option): [select] Option text display with extra long dots and dots,Added icon attribute, supporting custom icons. (#1190)
* feat(option): Option text display with extra long dots and dots * feat(option): [select] Added icon attribute, supporting custom icons * test(select): [select] Improve select component test cases * docs(select): [select] Add an instance demo displaying tag and tip scenarios * test(select): [select] Modify test casesoption
This commit is contained in:
parent
c746f53e53
commit
89624d0356
|
@ -1,22 +1,27 @@
|
|||
<template>
|
||||
<div>
|
||||
<p>选中的值为: {{ value }}</p>
|
||||
<p>场景1:标签式</p>
|
||||
<tiny-select v-model="value">
|
||||
<tiny-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"> </tiny-option>
|
||||
<tiny-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" :icon="item.icon">
|
||||
</tiny-option>
|
||||
</tiny-select>
|
||||
<p>场景1:配置式</p>
|
||||
<tiny-select v-model="value" :options="options"> </tiny-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { Select as TinySelect, Option as TinyOption } from '@opentiny/vue'
|
||||
import { iconFile } from '@opentiny/vue-icon'
|
||||
|
||||
const options = ref([
|
||||
{ value: '选项1', label: '黄金糕' },
|
||||
{ value: '选项2', label: '双皮奶' },
|
||||
{ value: '选项3', label: '蚵仔煎' },
|
||||
{ value: '选项4', label: '龙须面' },
|
||||
{ value: '选项5', label: '北京烤鸭' }
|
||||
{ value: '选项1', label: '黄金糕', icon: iconFile() },
|
||||
{ value: '选项2', label: '双皮奶', icon: iconFile() },
|
||||
{ value: '选项3', label: '蚵仔煎', icon: iconFile() },
|
||||
{ value: '选项4', label: '龙须面', icon: iconFile() },
|
||||
{ value: '选项5', label: '北京烤鸭', icon: iconFile() }
|
||||
])
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
|
|
@ -1,20 +1,45 @@
|
|||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('基础用法', async ({ page }) => {
|
||||
test('基础用法标签式', async ({ page }) => {
|
||||
await page.goto('select#basic-usage')
|
||||
const wrap = page.locator('#basic-usage')
|
||||
const input = wrap.locator('.tiny-input__inner')
|
||||
const dropdown = page.locator('.tiny-select-dropdown')
|
||||
const select = wrap.locator('.tiny-select').nth(0)
|
||||
const input = select.locator('.tiny-input__inner')
|
||||
const dropdown = page.locator('body > .tiny-select-dropdown')
|
||||
const option = dropdown.locator('.tiny-option')
|
||||
|
||||
await input.click()
|
||||
await dropdown.getByRole('listitem').filter({ hasText: '蚵仔煎' }).click()
|
||||
await option.filter({ hasText: '蚵仔煎' }).click()
|
||||
await expect(input).toHaveValue('蚵仔煎')
|
||||
await wrap.locator('.tiny-input__suffix svg').click()
|
||||
await select.locator('.tiny-input__suffix svg').click()
|
||||
await expect(page.getByRole('listitem').filter({ hasText: '蚵仔煎' })).toHaveClass(/selected/)
|
||||
await dropdown.getByRole('listitem').filter({ hasText: '北京烤鸭' }).click()
|
||||
await option.filter({ hasText: '北京烤鸭' }).click()
|
||||
await expect(input).toHaveValue('北京烤鸭')
|
||||
await input.click()
|
||||
await expect(dropdown.getByRole('listitem').filter({ hasText: '北京烤鸭' })).toHaveClass(/selected/)
|
||||
await wrap.click()
|
||||
await expect(page.locator('div').filter({ hasText: '黄金糕双皮奶蚵仔煎龙须面北京烤鸭' }).first()).toBeHidden()
|
||||
await expect(option.filter({ hasText: '北京烤鸭' })).toHaveClass(/selected/)
|
||||
await expect(option.locator('.tiny-option__icon')).toHaveCount(5)
|
||||
await option.nth(0).click()
|
||||
await expect(dropdown).toBeHidden()
|
||||
})
|
||||
|
||||
test('基础用法配置式', async ({ page }) => {
|
||||
await page.goto('select#basic-usage')
|
||||
const wrap = page.locator('#basic-usage')
|
||||
const select = wrap.locator('.tiny-select').nth(1)
|
||||
const input = select.locator('.tiny-input__inner')
|
||||
const dropdown = page.locator('body > .tiny-select-dropdown')
|
||||
const option = dropdown.locator('.tiny-option')
|
||||
|
||||
await input.click()
|
||||
await option.filter({ hasText: '蚵仔煎' }).click()
|
||||
await expect(input).toHaveValue('蚵仔煎')
|
||||
await select.locator('.tiny-input__suffix svg').click()
|
||||
await expect(page.getByRole('listitem').filter({ hasText: '蚵仔煎' })).toHaveClass(/selected/)
|
||||
await option.filter({ hasText: '北京烤鸭' }).click()
|
||||
await expect(input).toHaveValue('北京烤鸭')
|
||||
await input.click()
|
||||
await expect(option.filter({ hasText: '北京烤鸭' })).toHaveClass(/selected/)
|
||||
await expect(option.locator('.tiny-option__icon')).toHaveCount(5)
|
||||
await option.nth(0).click()
|
||||
await expect(dropdown).toBeHidden()
|
||||
})
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
<template>
|
||||
<div>
|
||||
<p>选中的值为: {{ value }}</p>
|
||||
<p>场景1:标签式</p>
|
||||
<tiny-select v-model="value">
|
||||
<tiny-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"> </tiny-option>
|
||||
<tiny-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" :icon="item.icon">
|
||||
</tiny-option>
|
||||
</tiny-select>
|
||||
<p>场景1:配置式</p>
|
||||
<tiny-select v-model="value" :options="options"> </tiny-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Select, Option } from '@opentiny/vue'
|
||||
import { iconFile } from '@opentiny/vue-icon'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -18,11 +23,11 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
options: [
|
||||
{ value: '选项1', label: '黄金糕' },
|
||||
{ value: '选项2', label: '双皮奶' },
|
||||
{ value: '选项3', label: '蚵仔煎' },
|
||||
{ value: '选项4', label: '龙须面' },
|
||||
{ value: '选项5', label: '北京烤鸭' }
|
||||
{ value: '选项1', label: '黄金糕', icon: iconFile() },
|
||||
{ value: '选项2', label: '双皮奶', icon: iconFile() },
|
||||
{ value: '选项3', label: '蚵仔煎', icon: iconFile() },
|
||||
{ value: '选项4', label: '龙须面', icon: iconFile() },
|
||||
{ value: '选项5', label: '北京烤鸭', icon: iconFile() }
|
||||
],
|
||||
value: ''
|
||||
}
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
<template>
|
||||
<tiny-select v-model="value" popper-class="slot-default">
|
||||
<tiny-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
|
||||
<span class="left">{{ item.label }}</span>
|
||||
<span class="right">{{ item.value }}</span>
|
||||
</tiny-option>
|
||||
<template v-for="item in options" :key="item.value">
|
||||
<tiny-tooltip :content="item.tip" placement="right" effect="light">
|
||||
<tiny-option :label="item.label" :value="item.value">
|
||||
<span class="left">{{ item.label }}</span>
|
||||
<tiny-tag v-if="item.tag" type="danger" effect="light" size="small">{{ item.tag }}</tiny-tag>
|
||||
</tiny-option>
|
||||
</tiny-tooltip>
|
||||
</template>
|
||||
</tiny-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { Select as TinySelect, Option as TinyOption } from '@opentiny/vue'
|
||||
import { Select as TinySelect, Option as TinyOption, Tag as TinyTag, Tooltip as TinyTooltip } from '@opentiny/vue'
|
||||
|
||||
const options = ref([
|
||||
{ value: '选项1', label: '黄金糕' },
|
||||
{ value: '选项1', label: '黄金糕', tag: 'New', tip: '自定义提示' },
|
||||
{ value: '选项2', label: '双皮奶' },
|
||||
{ value: '选项3', label: '蚵仔煎' },
|
||||
{ value: '选项4', label: '龙须面' },
|
||||
|
@ -29,9 +33,5 @@ const value = ref('')
|
|||
.left {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.right {
|
||||
color: #8492a6;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -10,5 +10,8 @@ test('选项插槽', async ({ page }) => {
|
|||
const option = dropdown.locator('.tiny-option')
|
||||
|
||||
await input.click()
|
||||
await expect(option.filter({ hasText: '选项1' })).toBeVisible()
|
||||
await expect(option.filter({ hasText: '黄金糕' })).toBeVisible()
|
||||
await expect(option.filter({ hasText: '黄金糕' }).locator('.tiny-tag')).toHaveText('New')
|
||||
await option.filter({ hasText: '黄金糕' }).hover()
|
||||
await expect(page.locator('body > .tiny-tooltip').filter({ hasText: '自定义提示' })).toBeVisible()
|
||||
})
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
<template>
|
||||
<tiny-select v-model="value" popper-class="slot-default">
|
||||
<tiny-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
|
||||
<span class="left">{{ item.label }}</span>
|
||||
<span class="right">{{ item.value }}</span>
|
||||
</tiny-option>
|
||||
<template v-for="item in options" :key="item.value">
|
||||
<tiny-tooltip :content="item.tip" placement="right" effect="light">
|
||||
<tiny-option :label="item.label" :value="item.value">
|
||||
<span class="left">{{ item.label }}</span>
|
||||
<tiny-tag v-if="item.tag" type="danger" effect="light" size="small">{{ item.tag }}</tiny-tag>
|
||||
</tiny-option>
|
||||
</tiny-tooltip>
|
||||
</template>
|
||||
</tiny-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Select, Option } from '@opentiny/vue'
|
||||
import { Select, Option, Tag, Tooltip } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinySelect: Select,
|
||||
TinyOption: Option
|
||||
TinyOption: Option,
|
||||
TinyTag: Tag,
|
||||
TinyTooltip: Tooltip
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: [
|
||||
{ value: '选项1', label: '黄金糕' },
|
||||
{ value: '选项1', label: '黄金糕', tag: 'New', tip: '自定义提示' },
|
||||
{ value: '选项2', label: '双皮奶' },
|
||||
{ value: '选项3', label: '蚵仔煎' },
|
||||
{ value: '选项4', label: '龙须面' },
|
||||
|
@ -38,9 +44,5 @@ export default {
|
|||
.left {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.right {
|
||||
color: #8492a6;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1050,6 +1050,85 @@ export default {
|
|||
'demoId': 'manual-focus-blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': 'option',
|
||||
'type': 'component',
|
||||
'props': [
|
||||
{
|
||||
'name': 'label',
|
||||
'type': 'string',
|
||||
'defaultValue': '',
|
||||
'desc': {
|
||||
'zh-CN': '选项的显示文本',
|
||||
'en-US': 'Display text for option'
|
||||
},
|
||||
'demoId': 'basic-usage'
|
||||
},
|
||||
{
|
||||
'name': 'value',
|
||||
'type': 'string',
|
||||
'defaultValue': '',
|
||||
'desc': {
|
||||
'zh-CN': '选项的值',
|
||||
'en-US': 'Value for option'
|
||||
},
|
||||
'demoId': 'basic-usage'
|
||||
},
|
||||
{
|
||||
'name': 'icon',
|
||||
'type': 'VueComponent',
|
||||
'defaultValue': '',
|
||||
'desc': {
|
||||
'zh-CN': '自定义选项的图标',
|
||||
'en-US': 'Customize icons for options'
|
||||
},
|
||||
'demoId': 'basic-usage'
|
||||
},
|
||||
{
|
||||
'name': 'disabled',
|
||||
'type': 'boolean',
|
||||
'defaultValue': 'false',
|
||||
'desc': {
|
||||
'zh-CN': '选项是否禁用',
|
||||
'en-US': 'Is the option disabled'
|
||||
},
|
||||
'demoId': 'disabled'
|
||||
},
|
||||
{
|
||||
'name': 'required',
|
||||
'type': 'boolean',
|
||||
'defaultValue': 'false',
|
||||
'desc': {
|
||||
'zh-CN': '选项是否必选',
|
||||
'en-US': 'Is it mandatory to select an option'
|
||||
},
|
||||
'demoId': ''
|
||||
}
|
||||
/*
|
||||
// TINY-TODO: 待确认是否暴露给用户使用
|
||||
{
|
||||
'name': 'visible',
|
||||
'type': 'boolean',
|
||||
'defaultValue': 'true',
|
||||
'desc': {
|
||||
'zh-CN': '选项是否可见',
|
||||
'en-US': 'Is the option visible'
|
||||
},
|
||||
'demoId': 'basic-usage'
|
||||
},
|
||||
{
|
||||
'name': 'highlight-class',
|
||||
'type': 'string',
|
||||
'defaultValue': '',
|
||||
'desc': {
|
||||
'zh-CN': '选项高亮类名',
|
||||
'en-US': 'Is the option visible'
|
||||
},
|
||||
'demoId': 'basic-usage'
|
||||
},
|
||||
*/
|
||||
]
|
||||
}
|
||||
],
|
||||
types: [
|
||||
|
@ -1060,6 +1139,9 @@ export default {
|
|||
interface IOption {
|
||||
value?: string | number
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
icon?: VueComponent
|
||||
required?:boolean
|
||||
}
|
||||
`
|
||||
},
|
||||
|
|
|
@ -55,9 +55,23 @@
|
|||
height: var(--ti-option-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--ti-option-padding-vertical) var(--ti-option-padding-horizontal);
|
||||
cursor: pointer;
|
||||
line-height: 1.5;
|
||||
box-sizing: border-box;
|
||||
margin-top: var(--ti-option-margin-top);
|
||||
border-radius: var(--ti-option-border-radius);
|
||||
|
||||
& > .@{option-prefix-cls}__label {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
& > .@{option-prefix-cls}__icon {
|
||||
font-size: var(--ti-option-icon-size);
|
||||
margin-right: var(--ti-option-icon-margin-right);
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
color: var(--ti-option-disabled-text-color);
|
||||
|
|
|
@ -2,7 +2,7 @@ export const tinyOptionSmbTheme = {
|
|||
'ti-option-height': 'var(--ti-common-size-height-normal)',
|
||||
'ti-option-font-size': 'var(--ti-common-font-size-1)',
|
||||
'ti-option-border-radius': 'var(--ti-common-border-radius-0)',
|
||||
'ti-option-selected-font-weight': 'var(--ti-common-font-weight-4)',
|
||||
'ti-option-selected-font-weight': 'var(--ti-common-font-weight-6)',
|
||||
'ti-option-padding-horizontal': 'var(--ti-common-space-4x)',
|
||||
'ti-option-icon-color-selected': 'var(--ti-common-color-icon-graybg-active)',
|
||||
'ti-option-checkbox-border-color-hover': '#191919',
|
||||
|
|
|
@ -55,4 +55,8 @@
|
|||
--ti-option-highlight-text-color: var(--ti-common-color-text-highlight, #526ecc);
|
||||
// 选择器下拉选项已选项高亮文本色
|
||||
--ti-option-highlight-selected-text-color: var(--ti-common-color-text-white, #fff);
|
||||
}
|
||||
// 选择器下拉选项图标右侧外边距
|
||||
--ti-option-icon-margin-right: var(--ti-common-space-2x, 8px);
|
||||
// 选择器下拉选项图标尺寸
|
||||
--ti-option-icon-size: var(--ti-common-font-size-2, 16px);
|
||||
}
|
||||
|
|
|
@ -18,19 +18,22 @@
|
|||
@popper-prefix-cls: ~'@{css-prefix}popper';
|
||||
@scrollbar-prefix-cls: ~'@{css-prefix}scrollbar';
|
||||
@input-predix-cls: ~'@{css-prefix}input';
|
||||
@option-prefix-cls: ~'@{css-prefix}option';
|
||||
|
||||
.@{select-dropdown-prefix-cls} {
|
||||
.component-css-vars-select-dropdown();
|
||||
|
||||
position: absolute;
|
||||
z-index: 1001;
|
||||
border: var(--ti-select-dropdown-border-weight) var(--ti-select-dropdown-border-style) var(--ti-select-dropdown-border-color);
|
||||
border: var(--ti-select-dropdown-border-weight) var(--ti-select-dropdown-border-style)
|
||||
var(--ti-select-dropdown-border-color);
|
||||
border-radius: var(--ti-select-dropdown-border-radius);
|
||||
background-color: var(--ti-select-dropdown-bg-color);
|
||||
box-shadow: var(--ti-select-dropdown-box-shadow);
|
||||
margin-top: var(--ti-select-dropdown-margin-top);
|
||||
box-sizing: border-box;
|
||||
padding: var(--ti-select-dropdown-padding-top) var(--ti-select-dropdown-padding-horizontal) var(--ti-select-dropdown-padding-bottom);
|
||||
padding: var(--ti-select-dropdown-padding-top) var(--ti-select-dropdown-padding-horizontal)
|
||||
var(--ti-select-dropdown-padding-bottom);
|
||||
|
||||
.@{tree-prefix-cls} {
|
||||
max-height: 300px;
|
||||
|
@ -46,7 +49,8 @@
|
|||
}
|
||||
|
||||
& &__search {
|
||||
margin: var(--ti-select-dropdown-search-margin-top) var(--ti-select-dropdown-search-margin-right) var(--ti-select-dropdown-search-margin-bottom) var(--ti-select-dropdown-search-margin-left);
|
||||
margin: var(--ti-select-dropdown-search-margin-top) var(--ti-select-dropdown-search-margin-right)
|
||||
var(--ti-select-dropdown-search-margin-bottom) var(--ti-select-dropdown-search-margin-left);
|
||||
width: var(--ti-select-dropdown-search-width);
|
||||
|
||||
.@{input-predix-cls}__inner {
|
||||
|
@ -103,7 +107,7 @@
|
|||
fill: var(--ti-select-dropdown-svg-hover-bg-color);
|
||||
}
|
||||
|
||||
&+div+.@{select-dropdown-prefix-cls}__empty-wrap {
|
||||
& + div + .@{select-dropdown-prefix-cls}__empty-wrap {
|
||||
padding-top: var(--ti-select-dropdown-search-empty-padding-top);
|
||||
padding-bottom: var(--ti-select-dropdown-search-empty-padding-bottom);
|
||||
}
|
||||
|
@ -160,21 +164,17 @@
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
& &__item {
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
& .@{option-prefix-cls} {
|
||||
padding: var(--ti-option-padding-vertical) var(--ti-option-padding-horizontal);
|
||||
margin-top: var(--ti-option-margin-top);
|
||||
border-radius: var(--ti-option-border-radius);
|
||||
}
|
||||
|
||||
& &__wrap.virtual {
|
||||
position: relative;
|
||||
margin-right: 0 !important;
|
||||
|
||||
& .@{select-dropdown-prefix-cls}__item {
|
||||
& .@{option-prefix-cls} {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,8 +35,9 @@
|
|||
>
|
||||
</tiny-checkbox>
|
||||
</span>
|
||||
<component v-if="icon" :is="icon" class="tiny-option__icon"></component>
|
||||
<slot>
|
||||
<span>{{ state.currentLabel }}</span>
|
||||
<span class="tiny-option__label">{{ state.currentLabel }}</span>
|
||||
</slot>
|
||||
</li>
|
||||
</template>
|
||||
|
@ -74,7 +75,8 @@ export default defineComponent({
|
|||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
icon: Object
|
||||
},
|
||||
setup(props, context) {
|
||||
return setup({ props, context, renderless, api, mono: true })
|
||||
|
|
|
@ -411,6 +411,7 @@
|
|||
:value="item[valueField]"
|
||||
:disabled="item.disabled"
|
||||
:required="item.required"
|
||||
:icon="item.icon"
|
||||
:highlight-class="item._highlightClass"
|
||||
:events="item.events"
|
||||
@mousedown.stop
|
||||
|
|
Loading…
Reference in New Issue