Skip to content

Commit

Permalink
feat: form component IconPicker (#5005)
Browse files Browse the repository at this point in the history
  • Loading branch information
mynetfan authored Dec 4, 2024
1 parent 935df71 commit e23486d
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 918 deletions.
4 changes: 3 additions & 1 deletion apps/web-antd/src/adapter/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';

import { globalShareState } from '@vben/common-ui';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';

import {
Expand Down Expand Up @@ -54,6 +54,7 @@ export type ComponentType =
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'InputPassword'
Expand Down Expand Up @@ -87,6 +88,7 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Expand Down
4 changes: 3 additions & 1 deletion apps/web-ele/src/adapter/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';

import { globalShareState } from '@vben/common-ui';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';

import {
Expand Down Expand Up @@ -45,6 +45,7 @@ export type ComponentType =
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
Expand Down Expand Up @@ -73,6 +74,7 @@ async function initComponentAdapter() {
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: ElDivider,
IconPicker,
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: ElRadioGroup,
Expand Down
4 changes: 3 additions & 1 deletion apps/web-naive/src/adapter/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';

import { globalShareState } from '@vben/common-ui';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';

import {
Expand Down Expand Up @@ -46,6 +46,7 @@ export type ComponentType =
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
Expand Down Expand Up @@ -75,6 +76,7 @@ async function initComponentAdapter() {
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: NDivider,
IconPicker,
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: NRadioGroup,
Expand Down
4 changes: 3 additions & 1 deletion docs/src/components/common-ui/vben-form.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';

import { globalShareState } from '@vben/common-ui';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';

import {
Expand Down Expand Up @@ -149,6 +149,7 @@ export type ComponentType =
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| 'IconPicker';
| BaseFormComponentType;

async function initComponentAdapter() {
Expand All @@ -166,6 +167,7 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<script setup lang="ts">
import { ref, useTemplateRef, watch, watchEffect } from 'vue';
import { computed, ref, watch, watchEffect } from 'vue';
import { usePagination } from '@vben/hooks';
import { EmptyIcon, Grip } from '@vben/icons';
import { EmptyIcon, Grip, listIcons } from '@vben/icons';
import { $t } from '@vben/locales';
import {
Button,
Input,
Pagination,
PaginationEllipsis,
PaginationFirst,
Expand All @@ -18,81 +20,124 @@ import {
VbenPopover,
} from '@vben-core/shadcn-ui';
import { refDebounced } from '@vueuse/core';
interface Props {
value?: string;
pageSize?: number;
prefix?: string;
/**
* 图标列表
*/
icons?: string[];
}
const props = withDefaults(defineProps<Props>(), {
value: '',
prefix: 'ant-design',
pageSize: 36,
icons: () => [],
});
const emit = defineEmits<{
change: [string];
'update:value': [string];
}>();
const refTrigger = useTemplateRef<HTMLElement>('refTrigger');
const modelValue = defineModel({ default: '', type: String });
const visible = ref(false);
const currentSelect = ref('');
const currentList = ref(props.icons);
const currentPage = ref(1);
const keyword = ref('');
const keywordDebounce = refDebounced(keyword, 300);
const currentList = computed(() => {
try {
if (props.prefix) {
const icons = listIcons('', props.prefix);
if (icons.length === 0) {
console.warn(`No icons found for prefix: ${props.prefix}`);
}
return icons;
} else {
return props.icons;
}
} catch (error) {
console.error('Failed to load icons:', error);
return [];
}
});
watch(
() => props.icons,
(newIcons) => {
currentList.value = newIcons;
},
{ immediate: true },
);
const showList = computed(() => {
return currentList.value.filter((item) =>
item.includes(keywordDebounce.value),
);
});
const { paginationList, total, setCurrentPage } = usePagination(
currentList,
showList,
props.pageSize,
);
watchEffect(() => {
currentSelect.value = props.value;
currentSelect.value = modelValue.value;
});
watch(
() => currentSelect.value,
(v) => {
emit('update:value', v);
emit('change', v);
},
);
const handleClick = (icon: string) => {
currentSelect.value = icon;
modelValue.value = icon;
close();
};
const handlePageChange = (page: number) => {
currentPage.value = page;
setCurrentPage(page);
};
function changeOpenState() {
refTrigger.value?.click?.();
function toggleOpenState() {
visible.value = !visible.value;
}
defineExpose({ changeOpenState });
function open() {
visible.value = true;
}
function close() {
visible.value = false;
}
defineExpose({ toggleOpenState, open, close });
</script>
<template>
<VbenPopover
v-model:open="visible"
:content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }"
content-class="p-0 pt-3"
>
<template #trigger>
<div ref="refTrigger">
<VbenIcon :icon="currentSelect || Grip" class="size-5" />
</div>
<slot :close="close" :icon="currentSelect" :open="open" name="trigger">
<div class="flex items-center gap-2">
<Input
:value="currentSelect"
class="flex-1 cursor-pointer"
v-bind="$attrs"
:placeholder="$t('ui.iconPicker.placeholder')"
/>
<VbenIcon :icon="currentSelect || Grip" class="size-8" />
</div>
</slot>
</template>
<div class="mb-2 flex w-full">
<Input
v-model="keyword"
:placeholder="$t('ui.iconPicker.search')"
class="mx-2"
/>
</div>

<template v-if="paginationList.length > 0">
<div class="grid max-h-[360px] w-full grid-cols-6 justify-items-center">
Expand Down
4 changes: 4 additions & 0 deletions packages/locales/src/langs/en-US/ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
"pointAriaLabel": "Click point",
"clickInOrder": "Please click in order"
},
"iconPicker": {
"placeholder": "Select an icon",
"search": "Search icon..."
},
"fallback": {
"pageNotFound": "Oops! Page Not Found",
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",
Expand Down
4 changes: 4 additions & 0 deletions packages/locales/src/langs/zh-CN/ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
"pointAriaLabel": "点击点",
"clickInOrder": "请依次点击"
},
"iconPicker": {
"placeholder": "选择一个图标",
"search": "搜索图标..."
},
"fallback": {
"pageNotFound": "哎呀!未找到页面",
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",
Expand Down
4 changes: 3 additions & 1 deletion playground/src/adapter/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';

import { globalShareState } from '@vben/common-ui';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';

import {
Expand Down Expand Up @@ -54,6 +54,7 @@ export type ComponentType =
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'InputPassword'
Expand Down Expand Up @@ -87,6 +88,7 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Expand Down
87 changes: 0 additions & 87 deletions playground/src/views/demos/features/icons/icon-picker.vue

This file was deleted.

Loading

0 comments on commit e23486d

Please sign in to comment.