Sfoglia il codice sorgente

feat: form component `IconPicker` (#5005)

Netfan 9 mesi fa
parent
commit
e23486dbc6

+ 3 - 1
apps/web-antd/src/adapter/component/index.ts

@@ -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 {
@@ -54,6 +54,7 @@ export type ComponentType =
   | 'DatePicker'
   | 'DefaultButton'
   | 'Divider'
+  | 'IconPicker'
   | 'Input'
   | 'InputNumber'
   | 'InputPassword'
@@ -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'),

+ 3 - 1
apps/web-ele/src/adapter/component/index.ts

@@ -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 {
@@ -45,6 +45,7 @@ export type ComponentType =
   | 'CheckboxGroup'
   | 'DatePicker'
   | 'Divider'
+  | 'IconPicker'
   | 'Input'
   | 'InputNumber'
   | 'RadioGroup'
@@ -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,

+ 3 - 1
apps/web-naive/src/adapter/component/index.ts

@@ -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 {
@@ -46,6 +46,7 @@ export type ComponentType =
   | 'CheckboxGroup'
   | 'DatePicker'
   | 'Divider'
+  | 'IconPicker'
   | 'Input'
   | 'InputNumber'
   | 'RadioGroup'
@@ -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,

+ 3 - 1
docs/src/components/common-ui/vben-form.md

@@ -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 {
@@ -149,6 +149,7 @@ export type ComponentType =
   | 'TimePicker'
   | 'TreeSelect'
   | 'Upload'
+  | 'IconPicker';
   | BaseFormComponentType;
 
 async function initComponentAdapter() {
@@ -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'),

+ 68 - 23
packages/effects/common-ui/src/components/icon-picker/icon-picker.vue

@@ -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,
@@ -18,9 +20,11 @@ import {
   VbenPopover,
 } from '@vben-core/shadcn-ui';
 
+import { refDebounced } from '@vueuse/core';
+
 interface Props {
-  value?: string;
   pageSize?: number;
+  prefix?: string;
   /**
    * 图标列表
    */
@@ -28,48 +32,65 @@ interface Props {
 }
 
 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) => {
@@ -77,22 +98,46 @@ const handlePageChange = (page: number) => {
   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">

+ 4 - 0
packages/locales/src/langs/en-US/ui.json

@@ -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.",

+ 4 - 0
packages/locales/src/langs/zh-CN/ui.json

@@ -21,6 +21,10 @@
     "pointAriaLabel": "点击点",
     "clickInOrder": "请依次点击"
   },
+  "iconPicker": {
+    "placeholder": "选择一个图标",
+    "search": "搜索图标..."
+  },
   "fallback": {
     "pageNotFound": "哎呀!未找到页面",
     "pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",

+ 3 - 1
playground/src/adapter/component/index.ts

@@ -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 {
@@ -54,6 +54,7 @@ export type ComponentType =
   | 'DatePicker'
   | 'DefaultButton'
   | 'Divider'
+  | 'IconPicker'
   | 'Input'
   | 'InputNumber'
   | 'InputPassword'
@@ -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'),

+ 0 - 87
playground/src/views/demos/features/icons/icon-picker.vue

@@ -1,87 +0,0 @@
-<script lang="ts" setup>
-import { computed, ref } from 'vue';
-
-import { IconPicker } from '@vben/common-ui';
-import { listIcons } from '@vben/icons';
-
-import { Input } from 'ant-design-vue';
-
-import iconsData from './icons.data';
-
-export interface Props {
-  allowClear?: boolean;
-  pageSize?: number;
-  /**
-   * 可以通过prefix获取系统中使用的图标集
-   */
-  prefix?: string;
-  readonly?: boolean;
-  value?: string;
-  width?: string;
-}
-
-// Don't inherit FormItem disabled、placeholder...
-defineOptions({
-  inheritAttrs: false,
-});
-
-const props = withDefaults(defineProps<Props>(), {
-  allowClear: true,
-  pageSize: 36,
-  prefix: '',
-  readonly: false,
-  value: '',
-  width: '100%',
-});
-
-const refIconPicker = ref();
-const currentSelect = ref('');
-
-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 {
-      const prefix = iconsData.prefix;
-      return iconsData.icons.map((icon) => `${prefix}:${icon}`);
-    }
-  } catch (error) {
-    console.error('Failed to load icons:', error);
-    return [];
-  }
-});
-
-const triggerPopover = () => {
-  refIconPicker.value?.changeOpenState?.();
-};
-
-const handleChange = (icon: string) => {
-  currentSelect.value = icon;
-};
-</script>
-
-<template>
-  <Input
-    v-model:value="currentSelect"
-    :allow-clear="props.allowClear"
-    :readonly="props.readonly"
-    :style="{ width }"
-    class="cursor-pointer"
-    placeholder="点击选中图标"
-    @click="triggerPopover"
-  >
-    <template #addonAfter>
-      <IconPicker
-        ref="refIconPicker"
-        :icons="currentList"
-        :page-size="pageSize"
-        :value="currentSelect"
-        @change="handleChange"
-      />
-    </template>
-  </Input>
-</template>

+ 0 - 793
playground/src/views/demos/features/icons/icons.data.ts

@@ -1,793 +0,0 @@
-export default {
-  icons: [
-    'account-book-filled',
-    'account-book-outlined',
-    'account-book-twotone',
-    'aim-outlined',
-    'alert-filled',
-    'alert-outlined',
-    'alert-twotone',
-    'alibaba-outlined',
-    'align-center-outlined',
-    'align-left-outlined',
-    'align-right-outlined',
-    'alipay-circle-filled',
-    'alipay-circle-outlined',
-    'alipay-outlined',
-    'alipay-square-filled',
-    'aliwangwang-filled',
-    'aliwangwang-outlined',
-    'aliyun-outlined',
-    'amazon-circle-filled',
-    'amazon-outlined',
-    'amazon-square-filled',
-    'android-filled',
-    'android-outlined',
-    'ant-cloud-outlined',
-    'ant-design-outlined',
-    'apartment-outlined',
-    'api-filled',
-    'api-outlined',
-    'api-twotone',
-    'apple-filled',
-    'apple-outlined',
-    'appstore-add-outlined',
-    'appstore-filled',
-    'appstore-outlined',
-    'appstore-twotone',
-    'area-chart-outlined',
-    'arrow-down-outlined',
-    'arrow-left-outlined',
-    'arrow-right-outlined',
-    'arrow-up-outlined',
-    'arrows-alt-outlined',
-    'audio-filled',
-    'audio-muted-outlined',
-    'audio-outlined',
-    'audio-twotone',
-    'audit-outlined',
-    'backward-filled',
-    'backward-outlined',
-    'bank-filled',
-    'bank-outlined',
-    'bank-twotone',
-    'bar-chart-outlined',
-    'barcode-outlined',
-    'bars-outlined',
-    'behance-circle-filled',
-    'behance-outlined',
-    'behance-square-filled',
-    'behance-square-outlined',
-    'bell-filled',
-    'bell-outlined',
-    'bell-twotone',
-    'bg-colors-outlined',
-    'block-outlined',
-    'bold-outlined',
-    'book-filled',
-    'book-outlined',
-    'book-twotone',
-    'border-bottom-outlined',
-    'border-horizontal-outlined',
-    'border-inner-outlined',
-    'border-left-outlined',
-    'border-outer-outlined',
-    'border-outlined',
-    'border-right-outlined',
-    'border-top-outlined',
-    'border-verticle-outlined',
-    'borderless-table-outlined',
-    'box-plot-filled',
-    'box-plot-outlined',
-    'box-plot-twotone',
-    'branches-outlined',
-    'bug-filled',
-    'bug-outlined',
-    'bug-twotone',
-    'build-filled',
-    'build-outlined',
-    'build-twotone',
-    'bulb-filled',
-    'bulb-outlined',
-    'bulb-twotone',
-    'calculator-filled',
-    'calculator-outlined',
-    'calculator-twotone',
-    'calendar-filled',
-    'calendar-outlined',
-    'calendar-twotone',
-    'camera-filled',
-    'camera-outlined',
-    'camera-twotone',
-    'car-filled',
-    'car-outlined',
-    'car-twotone',
-    'caret-down-filled',
-    'caret-down-outlined',
-    'caret-left-filled',
-    'caret-left-outlined',
-    'caret-right-filled',
-    'caret-right-outlined',
-    'caret-up-filled',
-    'caret-up-outlined',
-    'carry-out-filled',
-    'carry-out-outlined',
-    'carry-out-twotone',
-    'check-circle-filled',
-    'check-circle-outlined',
-    'check-circle-twotone',
-    'check-outlined',
-    'check-square-filled',
-    'check-square-outlined',
-    'check-square-twotone',
-    'chrome-filled',
-    'chrome-outlined',
-    'ci-circle-filled',
-    'ci-circle-outlined',
-    'ci-circle-twotone',
-    'ci-outlined',
-    'ci-twotone',
-    'clear-outlined',
-    'clock-circle-filled',
-    'clock-circle-outlined',
-    'clock-circle-twotone',
-    'close-circle-filled',
-    'close-circle-outlined',
-    'close-circle-twotone',
-    'close-outlined',
-    'close-square-filled',
-    'close-square-outlined',
-    'close-square-twotone',
-    'cloud-download-outlined',
-    'cloud-filled',
-    'cloud-outlined',
-    'cloud-server-outlined',
-    'cloud-sync-outlined',
-    'cloud-twotone',
-    'cloud-upload-outlined',
-    'cluster-outlined',
-    'code-filled',
-    'code-outlined',
-    'code-sandbox-circle-filled',
-    'code-sandbox-outlined',
-    'code-sandbox-square-filled',
-    'code-twotone',
-    'codepen-circle-filled',
-    'codepen-circle-outlined',
-    'codepen-outlined',
-    'codepen-square-filled',
-    'coffee-outlined',
-    'column-height-outlined',
-    'column-width-outlined',
-    'comment-outlined',
-    'compass-filled',
-    'compass-outlined',
-    'compass-twotone',
-    'compress-outlined',
-    'console-sql-outlined',
-    'contacts-filled',
-    'contacts-outlined',
-    'contacts-twotone',
-    'container-filled',
-    'container-outlined',
-    'container-twotone',
-    'control-filled',
-    'control-outlined',
-    'control-twotone',
-    'copy-filled',
-    'copy-outlined',
-    'copy-twotone',
-    'copyright-circle-filled',
-    'copyright-circle-outlined',
-    'copyright-circle-twotone',
-    'copyright-outlined',
-    'copyright-twotone',
-    'credit-card-filled',
-    'credit-card-outlined',
-    'credit-card-twotone',
-    'crown-filled',
-    'crown-outlined',
-    'crown-twotone',
-    'customer-service-filled',
-    'customer-service-outlined',
-    'customer-service-twotone',
-    'dash-outlined',
-    'dashboard-filled',
-    'dashboard-outlined',
-    'dashboard-twotone',
-    'database-filled',
-    'database-outlined',
-    'database-twotone',
-    'delete-column-outlined',
-    'delete-filled',
-    'delete-outlined',
-    'delete-row-outlined',
-    'delete-twotone',
-    'delivered-procedure-outlined',
-    'deployment-unit-outlined',
-    'desktop-outlined',
-    'diff-filled',
-    'diff-outlined',
-    'diff-twotone',
-    'dingding-outlined',
-    'dingtalk-circle-filled',
-    'dingtalk-outlined',
-    'dingtalk-square-filled',
-    'disconnect-outlined',
-    'dislike-filled',
-    'dislike-outlined',
-    'dislike-twotone',
-    'dollar-circle-filled',
-    'dollar-circle-outlined',
-    'dollar-circle-twotone',
-    'dollar-outlined',
-    'dollar-twotone',
-    'dot-chart-outlined',
-    'double-left-outlined',
-    'double-right-outlined',
-    'down-circle-filled',
-    'down-circle-outlined',
-    'down-circle-twotone',
-    'down-outlined',
-    'down-square-filled',
-    'down-square-outlined',
-    'down-square-twotone',
-    'download-outlined',
-    'drag-outlined',
-    'dribbble-circle-filled',
-    'dribbble-outlined',
-    'dribbble-square-filled',
-    'dribbble-square-outlined',
-    'dropbox-circle-filled',
-    'dropbox-outlined',
-    'dropbox-square-filled',
-    'edit-filled',
-    'edit-outlined',
-    'edit-twotone',
-    'ellipsis-outlined',
-    'enter-outlined',
-    'environment-filled',
-    'environment-outlined',
-    'environment-twotone',
-    'euro-circle-filled',
-    'euro-circle-outlined',
-    'euro-circle-twotone',
-    'euro-outlined',
-    'euro-twotone',
-    'exception-outlined',
-    'exclamation-circle-filled',
-    'exclamation-circle-outlined',
-    'exclamation-circle-twotone',
-    'exclamation-outlined',
-    'expand-alt-outlined',
-    'expand-outlined',
-    'experiment-filled',
-    'experiment-outlined',
-    'experiment-twotone',
-    'export-outlined',
-    'eye-filled',
-    'eye-invisible-filled',
-    'eye-invisible-outlined',
-    'eye-invisible-twotone',
-    'eye-outlined',
-    'eye-twotone',
-    'facebook-filled',
-    'facebook-outlined',
-    'fall-outlined',
-    'fast-backward-filled',
-    'fast-backward-outlined',
-    'fast-forward-filled',
-    'fast-forward-outlined',
-    'field-binary-outlined',
-    'field-number-outlined',
-    'field-string-outlined',
-    'field-time-outlined',
-    'file-add-filled',
-    'file-add-outlined',
-    'file-add-twotone',
-    'file-done-outlined',
-    'file-excel-filled',
-    'file-excel-outlined',
-    'file-excel-twotone',
-    'file-exclamation-filled',
-    'file-exclamation-outlined',
-    'file-exclamation-twotone',
-    'file-filled',
-    'file-gif-outlined',
-    'file-image-filled',
-    'file-image-outlined',
-    'file-image-twotone',
-    'file-jpg-outlined',
-    'file-markdown-filled',
-    'file-markdown-outlined',
-    'file-markdown-twotone',
-    'file-outlined',
-    'file-pdf-filled',
-    'file-pdf-outlined',
-    'file-pdf-twotone',
-    'file-ppt-filled',
-    'file-ppt-outlined',
-    'file-ppt-twotone',
-    'file-protect-outlined',
-    'file-search-outlined',
-    'file-sync-outlined',
-    'file-text-filled',
-    'file-text-outlined',
-    'file-text-twotone',
-    'file-twotone',
-    'file-unknown-filled',
-    'file-unknown-outlined',
-    'file-unknown-twotone',
-    'file-word-filled',
-    'file-word-outlined',
-    'file-word-twotone',
-    'file-zip-filled',
-    'file-zip-outlined',
-    'file-zip-twotone',
-    'filter-filled',
-    'filter-outlined',
-    'filter-twotone',
-    'fire-filled',
-    'fire-outlined',
-    'fire-twotone',
-    'flag-filled',
-    'flag-outlined',
-    'flag-twotone',
-    'folder-add-filled',
-    'folder-add-outlined',
-    'folder-add-twotone',
-    'folder-filled',
-    'folder-open-filled',
-    'folder-open-outlined',
-    'folder-open-twotone',
-    'folder-outlined',
-    'folder-twotone',
-    'folder-view-outlined',
-    'font-colors-outlined',
-    'font-size-outlined',
-    'fork-outlined',
-    'form-outlined',
-    'format-painter-filled',
-    'format-painter-outlined',
-    'forward-filled',
-    'forward-outlined',
-    'frown-filled',
-    'frown-outlined',
-    'frown-twotone',
-    'fullscreen-exit-outlined',
-    'fullscreen-outlined',
-    'function-outlined',
-    'fund-filled',
-    'fund-outlined',
-    'fund-projection-screen-outlined',
-    'fund-twotone',
-    'fund-view-outlined',
-    'funnel-plot-filled',
-    'funnel-plot-outlined',
-    'funnel-plot-twotone',
-    'gateway-outlined',
-    'gif-outlined',
-    'gift-filled',
-    'gift-outlined',
-    'gift-twotone',
-    'github-filled',
-    'github-outlined',
-    'gitlab-filled',
-    'gitlab-outlined',
-    'global-outlined',
-    'gold-filled',
-    'gold-outlined',
-    'gold-twotone',
-    'golden-filled',
-    'google-circle-filled',
-    'google-outlined',
-    'google-plus-circle-filled',
-    'google-plus-outlined',
-    'google-plus-square-filled',
-    'google-square-filled',
-    'group-outlined',
-    'hdd-filled',
-    'hdd-outlined',
-    'hdd-twotone',
-    'heart-filled',
-    'heart-outlined',
-    'heart-twotone',
-    'heat-map-outlined',
-    'highlight-filled',
-    'highlight-outlined',
-    'highlight-twotone',
-    'history-outlined',
-    'home-filled',
-    'home-outlined',
-    'home-twotone',
-    'hourglass-filled',
-    'hourglass-outlined',
-    'hourglass-twotone',
-    'html5-filled',
-    'html5-outlined',
-    'html5-twotone',
-    'idcard-filled',
-    'idcard-outlined',
-    'idcard-twotone',
-    'ie-circle-filled',
-    'ie-outlined',
-    'ie-square-filled',
-    'import-outlined',
-    'inbox-outlined',
-    'info-circle-filled',
-    'info-circle-outlined',
-    'info-circle-twotone',
-    'info-outlined',
-    'insert-row-above-outlined',
-    'insert-row-below-outlined',
-    'insert-row-left-outlined',
-    'insert-row-right-outlined',
-    'instagram-filled',
-    'instagram-outlined',
-    'insurance-filled',
-    'insurance-outlined',
-    'insurance-twotone',
-    'interaction-filled',
-    'interaction-outlined',
-    'interaction-twotone',
-    'issues-close-outlined',
-    'italic-outlined',
-    'key-outlined',
-    'laptop-outlined',
-    'layout-filled',
-    'layout-outlined',
-    'layout-twotone',
-    'left-circle-filled',
-    'left-circle-outlined',
-    'left-circle-twotone',
-    'left-outlined',
-    'left-square-filled',
-    'left-square-outlined',
-    'left-square-twotone',
-    'like-filled',
-    'like-outlined',
-    'like-twotone',
-    'line-chart-outlined',
-    'line-height-outlined',
-    'line-outlined',
-    'link-outlined',
-    'linkedin-filled',
-    'linkedin-outlined',
-    'loading-3-quarters-outlined',
-    'loading-outlined',
-    'lock-filled',
-    'lock-outlined',
-    'lock-twotone',
-    'login-outlined',
-    'logout-outlined',
-    'mac-command-filled',
-    'mac-command-outlined',
-    'mail-filled',
-    'mail-outlined',
-    'mail-twotone',
-    'man-outlined',
-    'medicine-box-filled',
-    'medicine-box-outlined',
-    'medicine-box-twotone',
-    'medium-circle-filled',
-    'medium-outlined',
-    'medium-square-filled',
-    'medium-workmark-outlined',
-    'meh-filled',
-    'meh-outlined',
-    'meh-twotone',
-    'menu-fold-outlined',
-    'menu-outlined',
-    'menu-unfold-outlined',
-    'merge-cells-outlined',
-    'message-filled',
-    'message-outlined',
-    'message-twotone',
-    'minus-circle-filled',
-    'minus-circle-outlined',
-    'minus-circle-twotone',
-    'minus-outlined',
-    'minus-square-filled',
-    'minus-square-outlined',
-    'minus-square-twotone',
-    'mobile-filled',
-    'mobile-outlined',
-    'mobile-twotone',
-    'money-collect-filled',
-    'money-collect-outlined',
-    'money-collect-twotone',
-    'monitor-outlined',
-    'more-outlined',
-    'node-collapse-outlined',
-    'node-expand-outlined',
-    'node-index-outlined',
-    'notification-filled',
-    'notification-outlined',
-    'notification-twotone',
-    'number-outlined',
-    'one-to-one-outlined',
-    'ordered-list-outlined',
-    'paper-clip-outlined',
-    'partition-outlined',
-    'pause-circle-filled',
-    'pause-circle-outlined',
-    'pause-circle-twotone',
-    'pause-outlined',
-    'pay-circle-filled',
-    'pay-circle-outlined',
-    'percentage-outlined',
-    'phone-filled',
-    'phone-outlined',
-    'phone-twotone',
-    'pic-center-outlined',
-    'pic-left-outlined',
-    'pic-right-outlined',
-    'picture-filled',
-    'picture-outlined',
-    'picture-twotone',
-    'pie-chart-filled',
-    'pie-chart-outlined',
-    'pie-chart-twotone',
-    'play-circle-filled',
-    'play-circle-outlined',
-    'play-circle-twotone',
-    'play-square-filled',
-    'play-square-outlined',
-    'play-square-twotone',
-    'plus-circle-filled',
-    'plus-circle-outlined',
-    'plus-circle-twotone',
-    'plus-outlined',
-    'plus-square-filled',
-    'plus-square-outlined',
-    'plus-square-twotone',
-    'pound-circle-filled',
-    'pound-circle-outlined',
-    'pound-circle-twotone',
-    'pound-outlined',
-    'poweroff-outlined',
-    'printer-filled',
-    'printer-outlined',
-    'printer-twotone',
-    'profile-filled',
-    'profile-outlined',
-    'profile-twotone',
-    'project-filled',
-    'project-outlined',
-    'project-twotone',
-    'property-safety-filled',
-    'property-safety-outlined',
-    'property-safety-twotone',
-    'pull-request-outlined',
-    'pushpin-filled',
-    'pushpin-outlined',
-    'pushpin-twotone',
-    'qq-circle-filled',
-    'qq-outlined',
-    'qq-square-filled',
-    'qrcode-outlined',
-    'question-circle-filled',
-    'question-circle-outlined',
-    'question-circle-twotone',
-    'question-outlined',
-    'radar-chart-outlined',
-    'radius-bottomleft-outlined',
-    'radius-bottomright-outlined',
-    'radius-setting-outlined',
-    'radius-upleft-outlined',
-    'radius-upright-outlined',
-    'read-filled',
-    'read-outlined',
-    'reconciliation-filled',
-    'reconciliation-outlined',
-    'reconciliation-twotone',
-    'red-envelope-filled',
-    'red-envelope-outlined',
-    'red-envelope-twotone',
-    'reddit-circle-filled',
-    'reddit-outlined',
-    'reddit-square-filled',
-    'redo-outlined',
-    'reload-outlined',
-    'rest-filled',
-    'rest-outlined',
-    'rest-twotone',
-    'retweet-outlined',
-    'right-circle-filled',
-    'right-circle-outlined',
-    'right-circle-twotone',
-    'right-outlined',
-    'right-square-filled',
-    'right-square-outlined',
-    'right-square-twotone',
-    'rise-outlined',
-    'robot-filled',
-    'robot-outlined',
-    'rocket-filled',
-    'rocket-outlined',
-    'rocket-twotone',
-    'rollback-outlined',
-    'rotate-left-outlined',
-    'rotate-right-outlined',
-    'safety-certificate-filled',
-    'safety-certificate-outlined',
-    'safety-certificate-twotone',
-    'safety-outlined',
-    'save-filled',
-    'save-outlined',
-    'save-twotone',
-    'scan-outlined',
-    'schedule-filled',
-    'schedule-outlined',
-    'schedule-twotone',
-    'scissor-outlined',
-    'search-outlined',
-    'security-scan-filled',
-    'security-scan-outlined',
-    'security-scan-twotone',
-    'select-outlined',
-    'send-outlined',
-    'setting-filled',
-    'setting-outlined',
-    'setting-twotone',
-    'shake-outlined',
-    'share-alt-outlined',
-    'shop-filled',
-    'shop-outlined',
-    'shop-twotone',
-    'shopping-cart-outlined',
-    'shopping-filled',
-    'shopping-outlined',
-    'shopping-twotone',
-    'shrink-outlined',
-    'signal-filled',
-    'sisternode-outlined',
-    'sketch-circle-filled',
-    'sketch-outlined',
-    'sketch-square-filled',
-    'skin-filled',
-    'skin-outlined',
-    'skin-twotone',
-    'skype-filled',
-    'skype-outlined',
-    'slack-circle-filled',
-    'slack-outlined',
-    'slack-square-filled',
-    'slack-square-outlined',
-    'sliders-filled',
-    'sliders-outlined',
-    'sliders-twotone',
-    'small-dash-outlined',
-    'smile-filled',
-    'smile-outlined',
-    'smile-twotone',
-    'snippets-filled',
-    'snippets-outlined',
-    'snippets-twotone',
-    'solution-outlined',
-    'sort-ascending-outlined',
-    'sort-descending-outlined',
-    'sound-filled',
-    'sound-outlined',
-    'sound-twotone',
-    'split-cells-outlined',
-    'star-filled',
-    'star-outlined',
-    'star-twotone',
-    'step-backward-filled',
-    'step-backward-outlined',
-    'step-forward-filled',
-    'step-forward-outlined',
-    'stock-outlined',
-    'stop-filled',
-    'stop-outlined',
-    'stop-twotone',
-    'strikethrough-outlined',
-    'subnode-outlined',
-    'swap-left-outlined',
-    'swap-outlined',
-    'swap-right-outlined',
-    'switcher-filled',
-    'switcher-outlined',
-    'switcher-twotone',
-    'sync-outlined',
-    'table-outlined',
-    'tablet-filled',
-    'tablet-outlined',
-    'tablet-twotone',
-    'tag-filled',
-    'tag-outlined',
-    'tag-twotone',
-    'tags-filled',
-    'tags-outlined',
-    'tags-twotone',
-    'taobao-circle-filled',
-    'taobao-circle-outlined',
-    'taobao-outlined',
-    'taobao-square-filled',
-    'team-outlined',
-    'thunderbolt-filled',
-    'thunderbolt-outlined',
-    'thunderbolt-twotone',
-    'to-top-outlined',
-    'tool-filled',
-    'tool-outlined',
-    'tool-twotone',
-    'trademark-circle-filled',
-    'trademark-circle-outlined',
-    'trademark-circle-twotone',
-    'trademark-outlined',
-    'transaction-outlined',
-    'translation-outlined',
-    'trophy-filled',
-    'trophy-outlined',
-    'trophy-twotone',
-    'twitter-circle-filled',
-    'twitter-outlined',
-    'twitter-square-filled',
-    'underline-outlined',
-    'undo-outlined',
-    'ungroup-outlined',
-    'unlock-filled',
-    'unlock-outlined',
-    'unlock-twotone',
-    'unordered-list-outlined',
-    'up-circle-filled',
-    'up-circle-outlined',
-    'up-circle-twotone',
-    'up-outlined',
-    'up-square-filled',
-    'up-square-outlined',
-    'up-square-twotone',
-    'upload-outlined',
-    'usb-filled',
-    'usb-outlined',
-    'usb-twotone',
-    'user-add-outlined',
-    'user-delete-outlined',
-    'user-outlined',
-    'user-switch-outlined',
-    'usergroup-add-outlined',
-    'usergroup-delete-outlined',
-    'verified-outlined',
-    'vertical-align-bottom-outlined',
-    'vertical-align-middle-outlined',
-    'vertical-align-top-outlined',
-    'vertical-left-outlined',
-    'vertical-right-outlined',
-    'video-camera-add-outlined',
-    'video-camera-filled',
-    'video-camera-outlined',
-    'video-camera-twotone',
-    'wallet-filled',
-    'wallet-outlined',
-    'wallet-twotone',
-    'warning-filled',
-    'warning-outlined',
-    'warning-twotone',
-    'wechat-filled',
-    'wechat-outlined',
-    'weibo-circle-filled',
-    'weibo-circle-outlined',
-    'weibo-outlined',
-    'weibo-square-filled',
-    'weibo-square-outlined',
-    'whats-app-outlined',
-    'wifi-outlined',
-    'windows-filled',
-    'windows-outlined',
-    'woman-outlined',
-    'yahoo-filled',
-    'yahoo-outlined',
-    'youtube-filled',
-    'youtube-outlined',
-    'yuque-filled',
-    'yuque-outlined',
-    'zhihu-circle-filled',
-    'zhihu-outlined',
-    'zhihu-square-filled',
-    'zoom-in-outlined',
-    'zoom-out-outlined',
-  ],
-  prefix: 'ant-design',
-};

+ 51 - 10
playground/src/views/demos/features/icons/index.vue

@@ -1,6 +1,9 @@
 <script lang="ts" setup>
-import { Page } from '@vben/common-ui';
+import { ref } from 'vue';
+
+import { IconPicker, Page } from '@vben/common-ui';
 import {
+  IconifyIcon,
   MdiGithub,
   MdiGoogle,
   MdiKeyboardEsc,
@@ -16,9 +19,9 @@ import {
   SvgDownloadIcon,
 } from '@vben/icons';
 
-import { Card } from 'ant-design-vue';
+import { Card, Input } from 'ant-design-vue';
 
-import IconPicker from './icon-picker.vue';
+const iconValue = ref('ant-design:trademark-outlined');
 </script>
 
 <template>
@@ -71,15 +74,53 @@ import IconPicker from './icon-picker.vue';
       </div>
     </Card>
 
-    <Card class="mb-5" title="图标选择器(Iconify)">
-      <div class="flex items-center gap-5">
-        <IconPicker width="300px" />
+    <Card class="mb-5" title="图标选择器">
+      <div class="mb-5 flex items-center gap-5">
+        <span>原始样式(Iconify):</span>
+        <IconPicker class="w-[200px]" />
+      </div>
+      <div class="mb-5 flex items-center gap-5">
+        <span>原始样式(svg):</span>
+        <IconPicker class="w-[200px]" prefix="svg" />
+      </div>
+      <div class="mb-5 flex items-center gap-5">
+        <span>完整替换触发组件:</span>
+        <IconPicker class="w-[200px]">
+          <template #trigger="{ icon }">
+            <Input
+              :value="icon"
+              placeholder="点击这里选择图标"
+              style="width: 300px"
+            >
+              <template #addonAfter>
+                <IconifyIcon
+                  :icon="icon || 'ant-design:appstore-filled'"
+                  class="text-2xl"
+                />
+              </template>
+            </Input>
+          </template>
+        </IconPicker>
       </div>
-    </Card>
-
-    <Card title="图标选择器(Svg)">
       <div class="flex items-center gap-5">
-        <IconPicker prefix="svg" width="300px" />
+        <span>可手动输入,只能点击图标打开弹窗:</span>
+        <Input
+          v-model:value="iconValue"
+          allow-clear
+          placeholder="点击这里选择图标"
+          style="width: 300px"
+        >
+          <template #addonAfter>
+            <IconPicker v-model="iconValue" class="w-[200px]">
+              <template #trigger="{ icon }">
+                <IconifyIcon
+                  :icon="icon || 'ant-design:appstore-filled'"
+                  class="text-2xl"
+                />
+              </template>
+            </IconPicker>
+          </template>
+        </Input>
       </div>
     </Card>
   </Page>

+ 5 - 0
playground/src/views/examples/form/basic.vue

@@ -57,6 +57,11 @@ const [BaseForm, baseFormApi] = useVbenForm({
       label: '数字(带后缀)',
       suffix: () => '¥',
     },
+    {
+      component: 'IconPicker',
+      fieldName: 'icon',
+      label: '图标',
+    },
     {
       component: 'Select',
       componentProps: {