Skip to content

Commit

Permalink
feat(pro:search): add customNameLabel and customOperatorLabel (#1491
Browse files Browse the repository at this point in the history
)
  • Loading branch information
sallerli1 committed Mar 6, 2023
1 parent e489e8e commit 1be87e8
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 21 deletions.
16 changes: 16 additions & 0 deletions packages/pro/search/demo/CustomLabel.md
@@ -0,0 +1,16 @@
---
order: 5
title:
zh: 自定义Label
en: Custom Label
---

## zh

使用 `customNameLabel` 自定义 `name` 下拉选择框的 label。
使用 `searchField.customOperatorLabel` 自定义 `operator` 下拉选择框的 label。

## en

Customize label of `name` select panel via `customNameLabel`.
Customize label of `operator` select panel via `searchField.customOperatorLabel`.
119 changes: 119 additions & 0 deletions packages/pro/search/demo/CustomLabel.vue
@@ -0,0 +1,119 @@
<template>
<IxProSearch
v-model:value="value"
style="width: 100%"
customNameLabel="nameLabel"
:searchFields="searchFields"
:onChange="onChange"
:onSearch="onSearch"
>
<template #levelOperator="operator">
<span>#{{ operator }}#</span>
</template>
<template #nameLabel="{ label }">
<span style="margin-right: 8px">{{ label }}</span>
<IxTooltip :title="`Select Search Field ${label}`">
<IxIcon name="exclamation-circle"></IxIcon>
</IxTooltip>
</template>
</IxProSearch>
</template>

<script setup lang="ts">
import type { SearchField, SearchValue } from '@idux/pro/search'
import { ref } from 'vue'
const value = ref<SearchValue[]>([
{
key: 'level',
name: 'Level',
operator: '=',
value: 'level1',
},
{
key: 'security_state',
name: 'Security State',
value: ['high', 'low'],
},
{
key: 'keyword',
name: '',
value: 'custom keyword',
},
])
const searchFields: SearchField[] = [
{
key: 'keyword',
type: 'input',
label: 'Keyword',
multiple: true,
placeholder: 'please input keyword',
fieldConfig: {
trim: true,
},
},
{
type: 'select',
label: 'Level',
key: 'level',
operators: ['=', '!='],
defaultOperator: '=',
customOperatorLabel: 'levelOperator',
fieldConfig: {
multiple: false,
searchable: true,
dataSource: [
{
key: 'level1',
label: 'Level 1',
},
{
key: 'level2',
label: 'Level 2',
},
{
key: 'level3',
label: 'Level 3',
},
],
},
},
{
type: 'select',
label: 'Security State',
key: 'security_state',
fieldConfig: {
multiple: true,
searchable: true,
dataSource: [
{
key: 'fatal',
label: 'fatal',
},
{
key: 'high',
label: 'high',
},
{
key: 'mediumn',
label: 'mediumn',
},
{
key: 'low',
label: 'low',
},
],
},
},
]
const onChange = (value: SearchValue[] | undefined, oldValue: SearchValue[] | undefined) => {
console.log(value, oldValue)
}
const onSearch = () => {
console.log('onSearch')
}
</script>

<style scoped lang="less"></style>
2 changes: 2 additions & 0 deletions packages/pro/search/docs/Api.zh.md
Expand Up @@ -9,6 +9,7 @@
| `v-model:errors` | 校验错误 | `{ index: number, message: string }` | - | - | - |
| `clearable` | 是否可清除 | `boolean` | `true` || - |
| `clearIcon` | 清除图标 | `string \| VNode \| #clearIcon` | `close-circle` || - |
| `customNameLabel` | 自定义搜索项名称下拉选择label | `string \| ((searchField: SearchField) => VNodeChild \| #nameLabel` | - | - | - |
| `disabled` | 是否禁用 | `boolean` | `false` | - | - |
| `overlayContainer` | 自定义浮层容器节点 | `string \| HTMLElement \| (trigger?: Element) => string \| HTMLElement` | - || - |
| `placeholder` | 默认文本 | `string` | - | - | - |
Expand Down Expand Up @@ -65,6 +66,7 @@ interface SearchItemConfirmContext<V = unknown> extends Partial<SearchValue<V>>
| `operators` | 搜索条件的中间操作符 | `string[]` | - | - | 提供时,会在搜索词条名称中间增加一个操作符,如 `'='`, `'!='` |
| `defaultOperator` | 默认的操作符 | `string` | - | - | 提供时,会自动填入默认的操作符 |
| `defaultValue` | 默认值 | - | - | - | 提供时,会自动填入默认值 |
| `customOperatorLabel` | 自定义操作符下拉选择label | `string \| ((operator: string) => VNodeChild)` | - | - | - |
| `inputClassName` | 输入框class | `string` | - | - | 用于自定义输入框样式 |
| `placeholder` | 输入框placeholder | `string` | - | - | 搜索值输入框的占位符 |
| `validator` | 搜索项校验函数 | `(value: SearchValue) => { message?: string } | undefined` | - | - | 返回错误信息 |
Expand Down
3 changes: 1 addition & 2 deletions packages/pro/search/src/ProSearch.tsx
Expand Up @@ -134,7 +134,6 @@ export default defineComponent({

provide(proSearchContext, {
props,
slots,
locale: locale.search,
mergedPrefixCls,
commonOverlayProps,
Expand Down Expand Up @@ -184,7 +183,7 @@ export default defineComponent({
/>
<div class={`${prefixCls}-search-item-container`} style={searchItemContainerStyle.value}>
{searchItems.value?.map(item => (
<SearchItemComp key={item.key} searchItem={item} />
<SearchItemComp key={item.key} v-slots={slots} searchItem={item} />
))}
</div>
{searchValueEmpty.value && !activeSegment.value && (
Expand Down
7 changes: 6 additions & 1 deletion packages/pro/search/src/composables/useSearchItem.ts
Expand Up @@ -38,7 +38,12 @@ export function useSearchItems(
)
const searchField = searchFields?.find(field => field.key === searchState.fieldKey)
const operatorSegment = searchField && createOperatorSegment(mergedPrefixCls.value, searchField)
const nameSegment = createNameSegment(mergedPrefixCls.value, searchFields, !operatorSegment)
const nameSegment = createNameSegment(
mergedPrefixCls.value,
searchFields,
props.customNameLabel,
!operatorSegment,
)

return {
key: searchState.key,
Expand Down
4 changes: 2 additions & 2 deletions packages/pro/search/src/panel/SelectPanel.tsx
Expand Up @@ -29,7 +29,7 @@ import { filterDataSource, matchRule } from '../utils/selectData'

export default defineComponent({
props: proSearchSelectPanelProps,
setup(props) {
setup(props, { slots }) {
const { locale, mergedPrefixCls } = inject(proSearchContext)!
const [activeValue, setActiveValue] = useState<VKey | undefined>(undefined)
const partiallySelected = computed(() => props.value && props.value.length > 0 && !props.allSelected)
Expand Down Expand Up @@ -159,7 +159,7 @@ export default defineComponent({
return (
<div class={prefixCls} tabindex={-1} onMousedown={evt => evt.preventDefault()}>
{renderSelectAll()}
<IxSelectPanel ref={panelRef} class={`${prefixCls}-inner`} {...panelProps} />
<IxSelectPanel ref={panelRef} class={`${prefixCls}-inner`} v-slots={slots} {...panelProps} />
{renderFooter()}
</div>
)
Expand Down
3 changes: 2 additions & 1 deletion packages/pro/search/src/searchItem/SearchItem.tsx
Expand Up @@ -17,7 +17,7 @@ import { searchItemProps } from '../types'

export default defineComponent({
props: searchItemProps,
setup(props) {
setup(props, { slots }) {
const context = inject(proSearchContext)!
const { props: proSearchProps, mergedPrefixCls, activeSegment } = context

Expand Down Expand Up @@ -63,6 +63,7 @@ export default defineComponent({
{segmentRenderDatas.value.map(segment => (
<Segment
key={segment.name}
v-slots={slots}
itemKey={props.searchItem!.key}
input={segment.input}
value={segment.value}
Expand Down
3 changes: 2 additions & 1 deletion packages/pro/search/src/searchItem/Segment.tsx
Expand Up @@ -29,7 +29,7 @@ import { type SegmentProps, segmentProps } from '../types'

export default defineComponent({
props: segmentProps,
setup(props: SegmentProps) {
setup(props: SegmentProps, { slots }) {
const context = inject(proSearchContext)!
const { mergedPrefixCls, commonOverlayProps, focused, activeSegment, searchStates, setActiveSegment } = context
const overlayRef = ref<ɵOverlayInstance>()
Expand Down Expand Up @@ -189,6 +189,7 @@ export default defineComponent({

const renderContent = () => {
const contentNode = props.segment.panelRenderer?.({
slots,
input: props.input ?? '',
value: props.value,
cancel: handleCancel,
Expand Down
5 changes: 1 addition & 4 deletions packages/pro/search/src/segments/CreateCascaderSegment.tsx
Expand Up @@ -88,7 +88,7 @@ export function createCascaderSegment(
expandIcon={expandIcon}
expandTrigger={expandTrigger}
fullPath={fullPath ?? defaultFullPath}
strategy={cascaderStrategy}
strategy={mergedCascaderStrategy}
separator={pathSeparator}
multiple={multiple}
virtual={virtual}
Expand Down Expand Up @@ -197,8 +197,6 @@ function getKeyByLabels(

const checkedKeys = checkedKeysResolver.appendKeys([], keys)

console.log(keys, checkedKeys)

if (!pathSeparator) {
return checkedKeys
}
Expand All @@ -211,7 +209,6 @@ function getKeyByLabels(
keys.unshift((currentKey = parentKeyMap.get(currentKey)!))
}

console.log('getKeys', keys)
return keys
}

Expand Down
26 changes: 24 additions & 2 deletions packages/pro/search/src/segments/CreateNameSegment.tsx
Expand Up @@ -5,7 +5,10 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import type { PanelRenderContext, SearchField, Segment } from '../types'
import type { PanelRenderContext, SearchField, Segment, SelectPanelData } from '../types'
import type { VNodeChild } from 'vue'

import { isString } from 'lodash-es'

import { type VKey, convertArray } from '@idux/cdk/utils'

Expand All @@ -17,11 +20,12 @@ export const defaultNameSegmentEndSymbol = ':'
export function createNameSegment(
prefixCls: string,
searchFields: SearchField[] | undefined,
customNameLabel: string | ((searchField: SearchField) => VNodeChild) | undefined,
showEndSymbol: boolean,
): Segment<VKey | undefined> {
const names = getSearchOptionNameList(searchFields ?? [])
const panelRenderer = (context: PanelRenderContext<VKey | undefined>) => {
const { input, value, setValue, ok, setOnKeyDown } = context
const { slots, input, value, setValue, ok, setOnKeyDown } = context
const handleChange = (value: VKey[]) => {
setValue(value[0])
ok()
Expand All @@ -31,9 +35,27 @@ export function createNameSegment(
return
}

const renderNameLabel = (key: VKey, renderer?: (searchField: SearchField) => VNodeChild) => {
if (!renderer) {
return undefined
}

const searchField = searchFields!.find(field => field.key === key)!
return renderer(searchField)
}

const _customNameLabel = customNameLabel ?? 'nameLabel'

const panelSlots = {
optionLabel: isString(_customNameLabel)
? (option: SelectPanelData) => renderNameLabel(option.key, slots[_customNameLabel])
: (option: SelectPanelData) => renderNameLabel(option.key, _customNameLabel),
}

return (
<SelectPanel
class={`${prefixCls}-name-segment-panel`}
v-slots={panelSlots}
value={convertArray(value)}
dataSource={filteredDataSource}
multiple={false}
Expand Down
21 changes: 17 additions & 4 deletions packages/pro/search/src/segments/CreateOperatorSegment.tsx
Expand Up @@ -5,7 +5,9 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import type { PanelRenderContext, SearchField, Segment } from '../types'
import type { PanelRenderContext, SearchField, Segment, SelectPanelData } from '../types'

import { isString } from 'lodash-es'

import { type VKey, convertArray } from '@idux/cdk/utils'

Expand All @@ -15,19 +17,30 @@ export function createOperatorSegment(
prefixCls: string,
searchField: SearchField,
): Segment<string | undefined> | undefined {
if (!searchField.operators || searchField.operators.length <= 0) {
const { operators, defaultOperator, customOperatorLabel } = searchField
if (!operators || operators.length <= 0) {
return
}

const panelRenderer = (context: PanelRenderContext<string | undefined>) => {
const { value, setValue, ok, setOnKeyDown } = context
const { slots, value, setValue, ok, setOnKeyDown } = context
const handleChange = (value: string[]) => {
setValue(value[0])
ok()
}

const panelSlots = {
optionLabel:
customOperatorLabel &&
(isString(customOperatorLabel)
? (option: SelectPanelData) => slots[customOperatorLabel]?.(option.key)
: (option: SelectPanelData) => customOperatorLabel?.(option.key as string)),
}

return (
<SelectPanel
class={`${prefixCls}-operator-segment-panel`}
v-slots={panelSlots}
value={convertArray(value)}
dataSource={searchField.operators?.map(operator => ({ key: operator, label: operator }))}
multiple={false}
Expand All @@ -41,7 +54,7 @@ export function createOperatorSegment(
return {
name: 'operator',
inputClassName: `${prefixCls}-operator-segment-input`,
defaultValue: searchField.operators.find(op => op === searchField.defaultOperator),
defaultValue: operators.find(op => op === defaultOperator),
parse: input => parseInput(input, searchField),
format: value => value ?? '',
panelRenderer,
Expand Down
3 changes: 1 addition & 2 deletions packages/pro/search/src/token.ts
Expand Up @@ -14,15 +14,14 @@ import type { SegmentStatesContext } from './composables/useSegmentStates'
import type { ProSearchProps } from './types'
import type { ɵOverlayProps } from '@idux/components/_private/overlay'
import type { ProSearchLocale } from '@idux/pro/locales'
import type { ComputedRef, InjectionKey, Slots } from 'vue'
import type { ComputedRef, InjectionKey } from 'vue'

export interface ProSearchContext
extends SearchStateContext,
SearchStateWatcherContext,
ActiveSegmentContext,
SearchTriggerContext {
props: ProSearchProps
slots: Slots
locale: ProSearchLocale
mergedPrefixCls: ComputedRef<string>
commonOverlayProps: ComputedRef<ɵOverlayProps>
Expand Down

0 comments on commit 1be87e8

Please sign in to comment.