Skip to content

Commit 8582902

Browse files
committed
chore: update icon list style
1 parent e690f4e commit 8582902

1 file changed

Lines changed: 124 additions & 26 deletions

File tree

packages/react/src/icon/demo/svg-icons.tsx

Lines changed: 124 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,139 @@
1+
import { useDeferredValue, useState } from 'react';
2+
import { IconSearch } from '@tiny-design/icons';
13
import * as Icons from '@tiny-design/icons';
24
import type { IconProps } from '@tiny-design/icons';
5+
import Input from '../../input';
6+
import Message from '../../message';
7+
import { useTheme } from '../../_utils/use-theme'
38

49
const iconEntries = Object.entries(Icons).filter(
510
([key]) => key.startsWith('Icon')
611
) as [string, React.FC<IconProps>][];
712

13+
const fallbackCopy = (value: string): void => {
14+
const textArea = document.createElement('textarea');
15+
textArea.style.position = 'fixed';
16+
textArea.style.opacity = '0';
17+
textArea.value = value;
18+
document.body.appendChild(textArea);
19+
textArea.select();
20+
document.execCommand('copy');
21+
document.body.removeChild(textArea);
22+
};
23+
24+
const copyText = async (value: string): Promise<void> => {
25+
if (navigator.clipboard) {
26+
try {
27+
await navigator.clipboard.writeText(value);
28+
return;
29+
} catch {
30+
// Fall through to the legacy path when clipboard permissions fail.
31+
}
32+
}
33+
34+
fallbackCopy(value);
35+
};
36+
837
const SvgIconList = (): JSX.Element => {
38+
const [keyword, setKeyword] = useState('');
39+
const [activeIconName, setActiveIconName] = useState('');
40+
const deferredKeyword = useDeferredValue(keyword);
41+
const { resolvedTheme } = useTheme()
42+
43+
const normalizedKeyword = deferredKeyword.trim().toLowerCase();
44+
const filteredIconEntries = iconEntries.filter(([name]) =>
45+
name.toLowerCase().includes(normalizedKeyword)
46+
);
47+
const copyIconComponent = async (name: string): Promise<void> => {
48+
const componentSnippet = `<${name} />`;
49+
50+
try {
51+
await copyText(componentSnippet);
52+
Message.success(`${componentSnippet} copied`);
53+
} catch {
54+
Message.error('Copy failed');
55+
}
56+
};
57+
958
return (
10-
<ul
11-
style={{
12-
listStyle: 'none',
13-
display: 'flex',
14-
flexWrap: 'wrap',
15-
padding: 0,
16-
margin: 0,
17-
borderTop: '1px solid #eaeefb',
18-
borderLeft: '1px solid #eaeefb',
19-
}}>
20-
{iconEntries.map(([name, Component]) => (
21-
<li
22-
key={name}
59+
<div>
60+
<div style={{ marginBottom: 16 }}>
61+
<Input
62+
value={keyword}
63+
clearable
64+
onClearClick={() => setKeyword('')}
65+
onChange={(event) => setKeyword(event.target.value)}
66+
placeholder="Search icons by name"
67+
aria-label="Search icons by name"
68+
prefix={<IconSearch size={16} color="#8c8c8c" />}
2369
style={{
24-
width: '16.66%',
70+
width: '100%',
71+
maxWidth: 320,
72+
boxSizing: 'border-box',
73+
}}
74+
/>
75+
</div>
76+
{filteredIconEntries.length > 0 ? (
77+
<ul
78+
style={{
79+
listStyle: 'none',
2580
display: 'flex',
26-
flexDirection: 'column',
27-
justifyContent: 'center',
28-
alignItems: 'center',
29-
color: '#454545',
30-
borderRight: '1px solid #eaeefb',
31-
borderBottom: '1px solid #eaeefb',
32-
padding: '20px 0',
81+
flexWrap: 'wrap',
82+
padding: 0,
83+
margin: 0,
84+
}}>
85+
{filteredIconEntries.map(([name, Component]) => (
86+
<li
87+
key={name}
88+
onClick={() => void copyIconComponent(name)}
89+
onMouseEnter={() => setActiveIconName(name)}
90+
onMouseLeave={() => setActiveIconName('')}
91+
onFocus={() => setActiveIconName(name)}
92+
onBlur={() => setActiveIconName('')}
93+
onKeyDown={(event) => {
94+
if (event.key === 'Enter' || event.key === ' ') {
95+
event.preventDefault();
96+
void copyIconComponent(name);
97+
}
98+
}}
99+
role="button"
100+
tabIndex={0}
101+
aria-label={`Copy ${name} component`}
102+
style={{
103+
width: '16.66%',
104+
display: 'flex',
105+
flexDirection: 'column',
106+
justifyContent: 'center',
107+
alignItems: 'center',
108+
padding: '20px 0',
109+
cursor: 'pointer',
110+
boxShadow:
111+
activeIconName === name
112+
? resolvedTheme === 'dark' ? '0 10px 24px rgba(255, 255, 255, 0.12)' : '0 10px 24px rgba(15, 23, 42, 0.12)'
113+
: 'none',
114+
transform: activeIconName === name ? 'translateY(-2px)' : 'translateY(0)',
115+
transition:
116+
'box-shadow 160ms ease, transform 160ms ease, background-color 160ms ease',
117+
position: 'relative',
118+
zIndex: activeIconName === name ? 1 : 0,
119+
}}>
120+
<Component size={30} />
121+
<span style={{ marginTop: 8, fontSize: 12 }}>{name}</span>
122+
</li>
123+
))}
124+
</ul>
125+
) : (
126+
<div
127+
style={{
128+
padding: '24px 16px',
129+
border: '1px solid #eaeefb',
130+
color: '#737373',
131+
fontSize: 14,
33132
}}>
34-
<Component size={30} />
35-
<span style={{ marginTop: 8, fontSize: 12 }}>{name}</span>
36-
</li>
37-
))}
38-
</ul>
133+
No icons match "{keyword.trim()}".
134+
</div>
135+
)}
136+
</div>
39137
);
40138
};
41139

0 commit comments

Comments
 (0)