cd-icon-picker 1.2.4 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cd-icon-picker",
3
- "version": "1.2.4",
3
+ "version": "1.2.7",
4
4
  "description": "Vue 3 icon picker supporting TDesign & RemixIcon, with custom SVG via :diy-icon.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -5,7 +5,7 @@
5
5
  <span v-else-if="isSvgMode && currentSelect" class="flex items-center justify-center">
6
6
  <SvgIcon :name="currentSelect" :prefix="svgPrefix" />
7
7
  </span>
8
- <Icon v-else :icon="currentSelect || defaultIcon" />
8
+ <Icon v-else :icon="currentSelect || defaultIcon" :color="color" />
9
9
  </div>
10
10
  <t-input v-else :readonly="readonly" :style="{ width }" :placeholder="mode === 'text' ? placeholder : ''" class="cd-icon-picker" v-model="displayValue" @click="handleOpen">
11
11
  <template #prefixIcon>
@@ -13,22 +13,32 @@
13
13
  <span v-else-if="isSvgMode && currentSelect" class="cursor-pointer px-2 py-1 flex items-center">
14
14
  <SvgIcon :name="currentSelect" :prefix="svgPrefix" />
15
15
  </span>
16
- <Icon v-else :icon="currentSelect || defaultIcon" class="cursor-pointer px-2 py-1" />
16
+ <Icon v-else :icon="currentSelect || defaultIcon" :color="color" class="cursor-pointer px-2 py-1" />
17
17
  </template>
18
18
  </t-input>
19
19
  <teleport to="body">
20
- <t-dialog v-model:visible="visible" width="960px" :close-on-esc-keydown="true" :close-on-overlay-click="true" @confirm="handleConfirm" @cancel="handleCancel">
20
+ <t-dialog v-model:visible="visible" width="1100px" :close-on-esc-keydown="true" :close-on-overlay-click="true" @confirm="handleConfirm" @cancel="handleCancel">
21
21
  <template #header>
22
22
  <div class="cd-picker-header">
23
23
  <span style="font-size: 14px;font-weight: normal;">当前图标:</span>
24
- <img v-if="isImageUrl(currentSelect)" :src="currentSelect" :style="{ filter: color ? `drop-shadow(0 0 0 ${color})` : '' }" style="width: 20px; height: 20px; object-fit: contain;" />
24
+ <img v-if="isImageUrl(currentSelect)" :src="currentSelect" :style="{ filter: color ? `drop-shadow(0 0 0 ${color})` : '' }" style="width: 24px; height: 24px; object-fit: contain;" />
25
25
  <span v-else-if="isSvgMode && currentSelect" :style="{ color }">
26
- <SvgIcon :name="currentSelect" :prefix="svgPrefix" :size="'20'" />
26
+ <SvgIcon :name="currentSelect" :prefix="svgPrefix" :size="'24'" />
27
27
  </span>
28
- <Icon v-else :icon="currentSelect || defaultIcon" :size="'20'" :style="{ color }" />
29
- <input type="color" v-model="color" style="margin-left: 8px; cursor: pointer; width: 30px; height: 24px; border: 1px solid #ddd; border-radius: 4px;" />
30
- <span style="font-size: 14px; color: #666; margin-left: 12px;">预设:</span>
31
- <span v-for="c in presetColors" :key="c" @click="color = c" :style="{ backgroundColor: c }" class="preset-color"></span>
28
+ <Icon v-else :icon="currentSelect || defaultIcon" :size="'24'" :style="{ color }" />
29
+ <span v-if="currentSelect" style="font-size: 12px; color: #999; margin-left: 8px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ currentSelect }}</span>
30
+ </div>
31
+ </template>
32
+ <template #footer>
33
+ <div class="cd-picker-footer">
34
+ <div class="cd-picker-footer-colors">
35
+ <input type="color" v-model="color" class="cd-picker-color-input" />
36
+ <span v-for="c in presetColors" :key="c" @click="color = c" :style="{ backgroundColor: c }" :class="['preset-color', color === c && 'preset-color-active']"></span>
37
+ </div>
38
+ <div class="cd-picker-footer-actions">
39
+ <t-button theme="default" @click="handleCancel">取消</t-button>
40
+ <t-button theme="primary" @click="handleConfirm">确定</t-button>
41
+ </div>
32
42
  </div>
33
43
  </template>
34
44
  <div class="cd-picker-body">
@@ -44,7 +54,6 @@
44
54
  <div class="cd-picker-section-title">来源</div>
45
55
  <ul class="cd-picker-cats">
46
56
  <li :class="['cd-picker-cat', sourceType === 'remix' && 'active']" @click="sourceType = 'remix'">通用</li>
47
- <li :class="['cd-picker-cat', sourceType === 'tdesign' && 'active']" @click="sourceType = 'tdesign'">通用2</li>
48
57
  <li :class="['cd-picker-cat', sourceType === 'company' && 'active']" @click="sourceType = 'company'">企业</li>
49
58
  <li :class="['cd-picker-cat', sourceType === 'custom' && 'active']" @click="sourceType = 'custom'">自定义</li>
50
59
  </ul>
@@ -106,7 +115,7 @@ const props = defineProps({
106
115
  remixicon: { type: String, default: '' },
107
116
  svg: { type: String, default: '' },
108
117
  width: { type: String, default: '100%' },
109
- pageSize: { type: Number, default: 140 },
118
+ pageSize: { type: Number, default: 168 },
110
119
  copy: { type: Boolean, default: false },
111
120
  diyIcon: { type: [Array, Object] as PropType<string[] | { prefix: string; icons: string[]; categoryMap?: Record<string, string[]> }> },
112
121
  placeholder: { type: String, default: '选择图标' },
@@ -163,7 +172,8 @@ const displayValue = computed({
163
172
  watch(
164
173
  () => props.type,
165
174
  v => {
166
- sourceType.value = (v as any) || 'remix';
175
+ // backward compat: treat 'tdesign' as 'remix' since they're merged
176
+ sourceType.value = (v === 'tdesign' ? 'remix' : (v as any)) || 'remix';
167
177
  },
168
178
  { immediate: true },
169
179
  );
@@ -175,11 +185,7 @@ watch(
175
185
  { immediate: true },
176
186
  );
177
187
  watchEffect(() => {
178
- let v = props.value || '';
179
- if (sourceType.value === 'remix' && v && !/-line$|-fill$/.test(v)) {
180
- v = v + (styleType.value === 'filled' ? '-fill' : '-line');
181
- }
182
- currentSelect.value = v;
188
+ currentSelect.value = props.value || '';
183
189
  });
184
190
  watch([rawIcons, styleType, sourceType], () => {
185
191
  page.value = 1;
@@ -189,10 +195,10 @@ const categoryMap = computed(() => {
189
195
  if (props.categoryMap) return props.categoryMap as Record<string, string[]>;
190
196
  const d = props.diyIcon as any;
191
197
  if (!Array.isArray(d) && d?.categoryMap) return d.categoryMap as Record<string, string[]>;
192
- const map = (categories as any)[sourceType.value] || (categories as any).tdesign;
198
+ const map = (categories as any)[sourceType.value] || (categories as any).tdesign || {};
193
199
  return map as Record<string, string[]>;
194
200
  });
195
- const categoryOptions = computed(() => Object.keys(categoryMap.value).filter(k => !k.endsWith('fill')));
201
+ const categoryOptions = computed(() => Object.keys(categoryMap.value || {}).filter(k => !k.endsWith('fill')));
196
202
  const filteredIcons = computed(() => {
197
203
  if (sourceType.value === 'custom') {
198
204
  const list = customIcons.value;
@@ -225,10 +231,6 @@ const filteredIcons = computed(() => {
225
231
  } else {
226
232
  list = ((useFill ? map[`${categoryType.value}fill`] : map[categoryType.value]) || map[categoryType.value] || []) as string[];
227
233
  }
228
- if (sourceType.value === 'remix') {
229
- const suffix = useFill ? '-fill' : '-line';
230
- list = list.map(n => (/-line$|-fill$/.test(n) ? n : `${n}${suffix}`));
231
- }
232
234
  return searchText.value ? list.filter(i => i.includes(searchText.value)) : list;
233
235
  });
234
236
  const getTotal = computed(() => filteredIcons.value.length);
@@ -264,20 +266,13 @@ function handleConfirm() {
264
266
  visible.value = false;
265
267
  const icon = currentSelect.value;
266
268
  if (!icon) return;
267
- if (sourceType.value === 'tdesign') emit('update:tdesign', icon);
268
- else if (sourceType.value === 'remix') emit('update:remixicon', icon);
269
- else emit('update:svg', icon);
270
269
  emit('update:value', icon);
271
270
  emit('update:color', color.value);
272
271
  emit('change', sourceType.value as any, icon as any, color.value);
273
272
  }
274
273
  function handleCancel() {
275
274
  visible.value = false;
276
- let v = props.value || '';
277
- if (sourceType.value === 'remix' && v && !/-line$|-fill$/.test(v)) {
278
- v = v + (styleType.value === 'filled' ? '-fill' : '-line');
279
- }
280
- currentSelect.value = v;
275
+ currentSelect.value = props.value || '';
281
276
  }
282
277
  function handleAddCustomImage() {
283
278
  const url = customImageUrl.value.trim();
@@ -310,16 +305,22 @@ watch(
310
305
  .cd-picker-cat.active { background: #f2f3f5; color: #0052d9; }
311
306
  .cd-icon-only { display: flex; align-items: center; justify-content: center; border: 1px solid #e5e5e5; border-radius: 6px; padding: 6px; cursor: pointer; }
312
307
  .cd-icon-only:hover { border-color: var(--td-brand-color, #0052d9); }
313
- .cd-picker-main { width: 620px; }
308
+ .cd-picker-main { flex: 1; }
314
309
  .cd-picker-search { margin-bottom: 8px; }
315
310
  .cd-picker-content { border-top: 1px solid #eee; }
316
- .cd-picker-grid { list-style: none; margin: 0; padding: 10px 0; display: grid; grid-template-columns: repeat(10, 1fr); gap: 10px; }
311
+ .cd-picker-grid { list-style: none; margin: 0; padding: 10px 0; display: grid; grid-template-columns: repeat(12, 1fr); gap: 10px; }
317
312
  .cd-picker-item { display: flex; width: 30px; align-items: center; justify-content: center; border: 1px solid #e5e5e5; border-radius: 6px; padding: 8px; cursor: pointer; }
318
313
  .cd-picker-item.active { border-color: var(--td-brand-color, #0052d9); }
319
314
  .cd-picker-item:hover { border-color: var(--td-brand-color, #0052d9); }
320
315
  .cd-picker-pagination { display: flex; justify-content: center; padding: 8px; }
321
316
  .cd-picker-empty { padding: 24px; }
322
- .cd-picker-header { display: flex; align-items: center; }
323
- .preset-color { display: inline-block; width: 30px; height: 24px; border-radius: 4px; margin-left: 6px; cursor: pointer; border: 1px solid #ddd; transition: all 0.2s; }
317
+ .cd-picker-header { display: flex; align-items: center; gap: 6px; }
318
+ .cd-picker-footer { display: flex; align-items: center; justify-content: space-between; width: 100%; }
319
+ .cd-picker-footer-colors { display: flex; align-items: center; gap: 6px; }
320
+ .cd-picker-footer-actions { display: flex; align-items: center; gap: 8px; }
321
+ .cd-picker-color-input { cursor: pointer; width: 28px; height: 28px; border: 1px solid #ddd; border-radius: 6px; padding: 1px; background: none; }
322
+ .cd-picker-color-input:hover { border-color: var(--td-brand-color, #0052d9); }
323
+ .preset-color { display: inline-block; width: 28px; height: 28px; border-radius: 6px; cursor: pointer; border: 2px solid transparent; transition: all 0.2s; box-sizing: border-box; }
324
324
  .preset-color:hover { border-color: var(--td-brand-color, #0052d9); transform: scale(1.1); }
325
+ .preset-color-active { border-color: var(--td-brand-color, #0052d9); box-shadow: 0 0 0 2px rgba(0, 82, 217, 0.2); }
325
326
  </style>