docs: improve icon search expierence (#54617)

This commit is contained in:
legend80s 2025-08-07 21:41:30 +08:00 committed by GitHub
parent feaed51f09
commit 6de357d8f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 458 additions and 28 deletions

View file

@ -9,7 +9,9 @@ import debounce from 'lodash/debounce';
import Category from './Category';
import type { CategoriesKeys } from './fields';
import { categories } from './fields';
import { all, categories } from './fields';
import metaInfo from './meta';
import type { IconName, IconsMeta } from './meta';
import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
export enum ThemeType {
@ -46,7 +48,7 @@ const IconSearch: React.FC = () => {
const handleSearchIcon = debounce((e: React.ChangeEvent<HTMLInputElement>) => {
setDisplayState((prevState) => ({ ...prevState, searchKey: e.target.value }));
}, 300);
}, 400);
const handleChangeTheme = useCallback((value: ThemeType) => {
setDisplayState((prevState) => ({ ...prevState, theme: value as ThemeType }));
@ -54,16 +56,27 @@ const IconSearch: React.FC = () => {
const renderCategories = useMemo<React.ReactNode | React.ReactNode[]>(() => {
const { searchKey = '', theme } = displayState;
// console.log('displayState:', displayState);
// console.log('metaInfo:', metaInfo);
// loop over metaInfo to find all the icons which has searchKey in their tags
let normalizedSearchKey = searchKey?.trim();
const categoriesResult = Object.keys(categories)
.map((key) => {
if (normalizedSearchKey) {
normalizedSearchKey = normalizedSearchKey
.replace(/^<([a-z]*)\s\/>$/gi, (_, name) => name)
.replace(/(Filled|Outlined|TwoTone)$/, '')
.toLowerCase();
}
const tagMatchedCategoryObj = matchCategoriesFromTag(normalizedSearchKey, metaInfo);
// console.log('categoriesMatchedAgainstTag:', tagMatchedCategoryObj);
const namedMatchedCategoryObj = Object.keys(categories).reduce(
(acc, key) => {
let iconList = categories[key as CategoriesKeys];
if (searchKey) {
const matchKey = searchKey
.replace(/^<([a-z]*)\s\/>$/gi, (_, name) => name)
.replace(/(Filled|Outlined|TwoTone)$/, '')
.toLowerCase();
if (normalizedSearchKey) {
const matchKey = normalizedSearchKey;
iconList = iconList.filter((iconName) => iconName.toLowerCase().includes(matchKey));
}
@ -73,23 +86,44 @@ const IconSearch: React.FC = () => {
];
iconList = iconList.filter((icon) => !ignore.includes(icon));
return {
acc[key] = {
category: key,
icons: iconList
.map((iconName) => iconName + theme)
.filter((iconName) => allIcons[iconName]),
icons: iconList,
};
return acc;
},
{} as Record<string, MatchedCategory>,
);
// merge matched categories from tag search
const merged = mergeCategory(namedMatchedCategoryObj, tagMatchedCategoryObj);
// console.log(
// 'namedMatchedCategoryObj, tagMatchedCategoryObj:',
// namedMatchedCategoryObj,
// tagMatchedCategoryObj,
// );
// console.log('merged:', merged);
const matchedCategories = Object.values(merged)
.map((item) => {
item.icons = item.icons
.map((iconName) => iconName + theme)
.filter((iconName) => allIcons[iconName]);
return item;
})
.filter(({ icons }) => !!icons.length)
.map(({ category, icons }) => (
<Category
key={category}
title={category as CategoriesKeys}
theme={theme}
icons={icons}
newIcons={newIconNames}
/>
));
.filter(({ icons }) => !!icons.length);
// console.log('matchedCategories:', matchedCategories);
const categoriesResult = matchedCategories.map(({ category, icons }) => (
<Category
key={category}
title={category as CategoriesKeys}
theme={theme}
icons={icons}
newIcons={newIconNames}
/>
));
return categoriesResult.length ? categoriesResult : <Empty style={{ margin: '2em 0' }} />;
}, [displayState.searchKey, displayState.theme]);
@ -136,7 +170,10 @@ const IconSearch: React.FC = () => {
onChange={handleChangeTheme}
/>
<Input.Search
placeholder={intl.formatMessage({ id: 'app.docs.components.icon.search.placeholder' })}
placeholder={intl.formatMessage(
{ id: 'app.docs.components.icon.search.placeholder' },
{ total: all.length },
)}
style={{ flex: 1, marginInlineStart: 16 }}
allowClear
autoFocus
@ -151,3 +188,58 @@ const IconSearch: React.FC = () => {
};
export default IconSearch;
type MatchedCategory = {
category: string;
icons: string[];
};
function matchCategoriesFromTag(
searchKey: string,
metaInfo: IconsMeta,
): Record<string, MatchedCategory> {
if (!searchKey) {
return {};
}
return Object.keys(metaInfo).reduce(
(acc, key) => {
const icon = metaInfo[key as IconName];
const category = icon.category;
if (icon.tags.some((tag) => tag.toLowerCase().includes(searchKey))) {
if (acc[category]) {
// if category exists, push icon to icons array
acc[category].icons.push(key);
} else {
// if category does not exist, create a new entry
acc[category] = {
category,
icons: [key],
};
}
}
return acc;
},
{} as Record<string, MatchedCategory>,
);
}
function mergeCategory(
categoryA: Record<string, MatchedCategory>,
categoryB: Record<string, MatchedCategory>,
) {
const merged: Record<string, MatchedCategory> = { ...categoryA };
Object.keys(categoryB).forEach((key) => {
if (merged[key]) {
// merge icons array and remove duplicates
merged[key].icons = Array.from(new Set([...merged[key].icons, ...categoryB[key].icons]));
} else {
merged[key] = categoryB[key];
}
});
return merged;
}

View file

@ -1,6 +1,6 @@
import * as AntdIcons from '@ant-design/icons/lib/icons';
const all = Object.keys(AntdIcons)
export const all = Object.keys(AntdIcons)
.map((n) => n.replace(/(Outlined|Filled|TwoTone)$/, ''))
.filter((n, i, arr) => arr.indexOf(n) === i);

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial, 'ledger'],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial],
category: 'logo',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: check,
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: check,
category: 'suggestion',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,7 @@
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: ['time', 'watch', 'alarm'],
category: 'suggestion',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis, 'feedback', 'discussion', 'reply', 'opinion', 'note'],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,7 @@
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: ['复制', 'clone', 'duplicate'],
category: 'editor',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis],
category: 'editor',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...check],
category: 'suggestion',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis, 'vertical'],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial, 'safety', 'protection', 'security', 'shield'],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,7 @@
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: ['ai'],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...check, 'protection', 'security', 'shield', 'safety', 'privacy'],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...check, 'time', 'clock', 'calendar'],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,8 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial],
category: 'other',
} as const satisfies IconMetaSchema;

View file

@ -0,0 +1,62 @@
import AccountBook from './AccountBook';
import Alipay from './Alipay';
import Bank from './Bank';
import CarryOut from './CarryOut';
import Check from './Check';
import ClockSquare from './ClockSquare';
import Comment from './Comment';
import Copy from './Copy';
import CreditCard from './CreditCard';
import Dash from './Dash';
import Dollar from './Dollar';
import Ellipsis from './Ellipsis';
import Euro from './Euro';
import Holder from './Holder';
import IssuesClose from './IssuesClose';
import More from './More';
import PayCircle from './PayCircle';
import PoundCircle from './PoundCircle';
import PropertySafety from './PropertySafety';
import RedEnvelope from './RedEnvelope';
import Robot from './Robot';
import Safety from './Safety';
import Schedule from './Schedule';
import Transaction from './Transaction';
const all = {
AccountBook,
Alipay,
AlipayCircle: Alipay,
Bank,
CarryOut,
Check,
CheckCircle: Check,
CheckSquare: Check,
ClockSquare,
Comment,
Copy,
CreditCard,
Dash,
SmallDash: Dash,
Dollar,
Ellipsis,
Euro,
EuroCircle: Euro,
Holder,
IssuesClose,
More,
PayCircle,
PoundCircle,
Pound: PoundCircle,
PropertySafety,
RedEnvelope,
Robot,
Safety,
Schedule,
Transaction,
};
export type IconsMeta = typeof all;
export type IconName = keyof IconsMeta;
export default all;

View file

@ -0,0 +1,87 @@
import type { CategoriesKeys } from '../fields';
export type IconMetaSchema = Readonly<{
contributors: string[];
tags: readonly string[];
category: CategoriesKeys;
}>;
export const check = [
'check',
'done',
'todo',
'tick',
'complete',
'finish',
'task',
'ok',
'success',
'confirm',
'approve',
'agree',
'validation',
'√',
'✔',
'✓',
'勾',
'对',
'正确',
'right',
] as const;
export const financial = [
'monetization',
'marketing',
'currency',
'money',
'payment',
'finance',
'cash',
'bank',
'transaction',
'balance',
'expense',
'income',
'budget',
'investment',
'savings',
'profit',
'cost',
'wealth',
'economy',
'wallet',
'exchange',
] as const;
export const ellipsis = [
'...',
'。。。',
'…',
'more',
'更多',
'dots',
'ellipsis',
'expand',
'collapse',
'menu',
'dropdown',
'options',
'settings',
'et cetera',
'etc',
'loader',
'loading',
'progress',
'pending',
'throbber',
'spinner',
'operator',
'code',
'spread',
'rest',
'further',
'extra',
'overflow',
] as const;

View file

@ -117,7 +117,7 @@
"app.footer.weavefox.slogan": "AI Development with WeaveFox 🦊",
"app.docs.color.pick-primary": "Pick your primary color",
"app.docs.color.pick-background": "Pick your background color",
"app.docs.components.icon.search.placeholder": "Search icons here, click icon to copy code",
"app.docs.components.icon.search.placeholder": "Search {total} icons here, click icon to copy code",
"app.docs.components.icon.outlined": "Outlined",
"app.docs.components.icon.filled": "Filled",
"app.docs.components.icon.two-tone": "Two Tone",

View file

@ -116,7 +116,7 @@
"app.footer.weavefox.slogan": "前端智能研发",
"app.docs.color.pick-primary": "选择你的主色",
"app.docs.color.pick-background": "选择你的背景色",
"app.docs.components.icon.search.placeholder": "在此搜索图标,点击图标可复制代码",
"app.docs.components.icon.search.placeholder": "在此搜索 {total} 个图标,点击图标可复制代码",
"app.docs.components.icon.outlined": "线框风格",
"app.docs.components.icon.filled": "实底风格",
"app.docs.components.icon.two-tone": "双色风格",