Skip to content

Commit 89fdcd7

Browse files
author
xingyan
committed
feat(CategorySearch): 选择项较多时,支持折叠
1 parent 010a711 commit 89fdcd7

9 files changed

Lines changed: 209 additions & 38 deletions

File tree

packages/devui-vue/devui/category-search/src/category-search-types.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ExtractPropTypes, PropType, InjectionKey, SetupContext, Ref } from 'vue';
2+
import type { PopoverProps } from '../../popover';
23

34
export type CategorySearchTagType = 'radio' | 'checkbox' | 'label' | 'textInput' | 'numberRange' | 'keyword';
45
export type StyleType = 'default' | 'gray';
@@ -131,6 +132,9 @@ export interface ExtendConfig {
131132
export interface ITagContext {
132133
toggle: (status?: boolean) => void;
133134
}
135+
export interface SelectedCategoryEvent {
136+
tag: ICategorySearchTagItem;
137+
}
134138

135139
export const categorySearchProps = {
136140
category: {
@@ -190,7 +194,7 @@ export const categorySearchProps = {
190194
filterTitle: '',
191195
labelConnector: '|',
192196
noCategoriesAvailable: '',
193-
tagMenuEmpty: ''
197+
tagMenuEmpty: '',
194198
}),
195199
},
196200
extendConfig: {
@@ -202,12 +206,19 @@ export const categorySearchProps = {
202206
},
203207
appendToBody: {
204208
type: Boolean,
205-
default: true
209+
default: true,
206210
},
207211
inputAutofocus: {
208212
type: Boolean,
209-
default: true
210-
}
213+
default: true,
214+
},
215+
collapseTags: {
216+
type: [Boolean, Number],
217+
default: false,
218+
},
219+
collapseTagsTooltip: {
220+
type: Object as PropType<PopoverProps>,
221+
},
211222
};
212223
export type CategorySearchProps = ExtractPropTypes<typeof categorySearchProps>;
213224

packages/devui-vue/devui/category-search/src/category-search.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ import CategorySearchMore from './components/category-search-more';
99
import { categorySearchProps } from './category-search-types';
1010
import type { CategorySearchProps } from './category-search-types';
1111
import { useCategorySearch } from './composables/use-category-search';
12+
import { useCategorySelectedTags } from './composables/use-category-selected-tags';
13+
import { Popover } from '../../popover';
14+
import { Tag } from '../../tag';
1215
import './category-search.scss';
1316

1417
export default defineComponent({
1518
name: 'DCategorySearch',
1619
props: categorySearchProps,
17-
emits: ['search', 'selectedTagsChange', 'createFilter', 'clearAll', 'searchKeyChange'],
20+
emits: ['search', 'selectedTagsChange', 'selectedCategory', 'createFilter', 'clearAll', 'searchKeyChange'],
1821
setup(props: CategorySearchProps, ctx: SetupContext) {
1922
const {
2023
rootRef,
@@ -28,6 +31,7 @@ export default defineComponent({
2831
operationConfig,
2932
onSearch,
3033
} = useCategorySearch(props, ctx);
34+
const { isCollapseTags, collapseTagsCount, popoverConfig } = useCategorySelectedTags(props);
3135

3236
return () => (
3337
<div
@@ -42,9 +46,31 @@ export default defineComponent({
4246
)}
4347
<div ref={scrollBarRef} class="dp-category-search-line-container">
4448
<ul class="dp-category-search-line">
45-
{innerSelectedTags.value.map((item) => (
46-
<CategorySearchTagDropdown item={item} isJoinLabelType={joinLabelTypes.includes(item.type || '')} />
47-
))}
49+
{innerSelectedTags.value.map((item, index) => {
50+
if (isCollapseTags.value) {
51+
return (
52+
index < collapseTagsCount.value && (
53+
<CategorySearchTagDropdown item={item} isJoinLabelType={joinLabelTypes.includes(item.type || '')} />
54+
)
55+
);
56+
} else {
57+
return <CategorySearchTagDropdown item={item} isJoinLabelType={joinLabelTypes.includes(item.type || '')} />;
58+
}
59+
})}
60+
{isCollapseTags.value && innerSelectedTags.value.length > collapseTagsCount.value && (
61+
<Popover auto-update-position {...popoverConfig.value}>
62+
{{
63+
default: () => <Tag deletable={false}>{`+${innerSelectedTags.value.length - collapseTagsCount.value}`}</Tag>,
64+
content: () =>
65+
innerSelectedTags.value.map(
66+
(item, index) =>
67+
index >= collapseTagsCount.value && (
68+
<CategorySearchTagDropdown item={item} isJoinLabelType={joinLabelTypes.includes(item.type || '')} />
69+
)
70+
),
71+
}}
72+
</Popover>
73+
)}
4874
<CategorySearchInput ref={inputRef} />
4975
</ul>
5076
</div>

packages/devui-vue/devui/category-search/src/composables/use-category-search.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function useCategorySearch(props: CategorySearchProps, ctx: SetupContext)
3838
showSearchCategory,
3939
filterNameRules,
4040
extendConfig,
41-
appendToBody
41+
appendToBody,
4242
} = toRefs(props);
4343
const innerCategory: Ref<ICategorySearchTagItem[]> = ref([]);
4444
const innerSelectedTags: Ref<ICategorySearchTagItem[]> = ref([]);
@@ -210,6 +210,7 @@ export function useCategorySearch(props: CategorySearchProps, ctx: SetupContext)
210210
currentSelectTag.value.title = setTitle(currentSelectTag.value, currentSelectTag.value.type || '', '');
211211
inputRef.value.openMenu();
212212
props.inputAutofocus && inputRef.value.focus();
213+
ctx.emit('selectedCategory', { tag: item });
213214
}, DROPDOWN_ANIMATION_TIMEOUT);
214215
};
215216

@@ -250,6 +251,7 @@ export function useCategorySearch(props: CategorySearchProps, ctx: SetupContext)
250251
innerSearchKey.value = '';
251252
inputRef.value.closeMenu();
252253
chooseCategory(tag);
254+
ctx.emit('selectedCategory', { tag });
253255
setTimeout(() => {
254256
isFocus.value = true;
255257
enterSearch.value = false;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { ref, watch } from 'vue';
2+
import type { CategorySearchProps } from '../category-search-types';
3+
4+
export function useCategorySelectedTags(props: CategorySearchProps) {
5+
const isCollapseTags = ref(false);
6+
const collapseTagsCount = ref(0);
7+
const defaultPopoverConfig = {
8+
trigger: 'hover',
9+
position: ['top', 'bottom'],
10+
};
11+
const popoverConfig = ref(defaultPopoverConfig);
12+
13+
watch(
14+
() => props.collapseTags,
15+
(val) => {
16+
if (typeof val === 'boolean') {
17+
isCollapseTags.value = val;
18+
collapseTagsCount.value = val ? 1 : 0;
19+
}
20+
if (typeof val === 'number') {
21+
isCollapseTags.value = true;
22+
collapseTagsCount.value = val;
23+
}
24+
},
25+
{ immediate: true }
26+
);
27+
28+
watch(
29+
() => props.collapseTagsTooltip,
30+
(val) => {
31+
if (val) {
32+
popoverConfig.value = { ...defaultPopoverConfig, ...val };
33+
}
34+
},
35+
{ immediate: true }
36+
);
37+
38+
return { isCollapseTags, collapseTagsCount, popoverConfig };
39+
}

packages/devui-vue/devui/overlay/src/flexible-overlay/flexible-overlay-types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ export const flexibleOverlayProps = {
6969
type: Boolean,
7070
default: false,
7171
},
72+
autoUpdatePosition: {
73+
type: Boolean,
74+
default: false,
75+
},
7276
};
7377

7478
export type FlexibleOverlayProps = ExtractPropTypes<typeof flexibleOverlayProps>;

packages/devui-vue/devui/overlay/src/flexible-overlay/use-flexible-overlay.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export function useOverlay(props: FlexibleOverlayProps, emit: EmitEventFn) {
2929
const arrowRef = ref<HTMLElement | undefined>();
3030
const overlayWidth = ref(0);
3131
let originObserver: ResizeObserver;
32+
let overlayObserver: ResizeObserver;
3233

3334
const styles = computed(() => {
3435
if (fitOriginWidth.value) {
@@ -104,6 +105,17 @@ export function useOverlay(props: FlexibleOverlayProps, emit: EmitEventFn) {
104105
originEl && originObserver?.unobserve(originEl);
105106
};
106107

108+
const observeOverlay = () => {
109+
if (props.autoUpdatePosition && typeof window !== 'undefined' && overlayRef.value) {
110+
overlayObserver = new window.ResizeObserver(updatePosition);
111+
overlayObserver.observe(overlayRef.value);
112+
}
113+
};
114+
115+
const unobserveOverlay = () => {
116+
overlayRef.value && overlayObserver?.unobserve(overlayRef.value);
117+
};
118+
107119
watch(
108120
() => props.modelValue,
109121
() => {
@@ -112,17 +124,20 @@ export function useOverlay(props: FlexibleOverlayProps, emit: EmitEventFn) {
112124
window.addEventListener('scroll', scrollCallback, true);
113125
window.addEventListener('resize', updatePosition);
114126
observeOrigin();
127+
nextTick(observeOverlay);
115128
} else {
116129
window.removeEventListener('scroll', scrollCallback, true);
117130
window.removeEventListener('resize', updatePosition);
118131
unobserveOrigin();
132+
unobserveOverlay();
119133
}
120134
}
121135
);
122136
onUnmounted(() => {
123137
window.removeEventListener('scroll', scrollCallback, true);
124138
window.removeEventListener('resize', updatePosition);
125139
unobserveOrigin();
140+
unobserveOverlay();
126141
});
127142

128143
return { arrowRef, overlayRef, styles, updatePosition };

0 commit comments

Comments
 (0)