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:
MomoPoppy 2023-12-21 16:28:00 +08:00 committed by GitHub
parent c746f53e53
commit 89624d0356
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 202 additions and 59 deletions

View File

@ -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>

View File

@ -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()
})

View File

@ -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: ''
}

View File

@ -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>

View File

@ -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()
})

View File

@ -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>

View File

@ -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
}
`
},

View File

@ -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);

View File

@ -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',

View File

@ -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);
}

View File

@ -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%;
}
}
}
}

View File

@ -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 })

View File

@ -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