@@ -16,6 +16,85 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
1616
1717export const iconList = Object . keys ( icons ) . map ( name => ( { id : name . replace ( / ^ S 2 _ I c o n _ ( .* ?) ( S i z e \d + ) ? _ 2 .* / , '$1' ) , icon : icons [ name ] . default } ) ) ;
1818
19+ export function useIconFilter ( ) {
20+ let { contains} = useFilter ( { sensitivity : 'base' } ) ;
21+ return useCallback ( ( textValue : string , inputValue : string ) => {
22+ // Check for alias matches
23+ for ( const alias of Object . keys ( iconAliases ) ) {
24+ if ( contains ( alias , inputValue ) && iconAliases [ alias ] . includes ( textValue ) ) {
25+ return true ;
26+ }
27+ }
28+ // Also compare for substrings in the icon's actual name
29+ return textValue != null && contains ( textValue , inputValue ) ;
30+ } , [ contains ] ) ;
31+ }
32+
33+ export function useCopyImport ( ) {
34+ let [ copiedId , setCopiedId ] = useState < string | null > ( null ) ;
35+ let timeout = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
36+
37+ useEffect ( ( ) => {
38+ return ( ) => {
39+ if ( timeout . current ) {
40+ clearTimeout ( timeout . current ) ;
41+ }
42+ } ;
43+ } , [ ] ) ;
44+
45+ let handleCopyImport = useCallback ( ( id : string ) => {
46+ if ( timeout . current ) {
47+ clearTimeout ( timeout . current ) ;
48+ }
49+ navigator . clipboard . writeText ( `import ${ id } from '@react-spectrum/s2/icons/${ id } ';` ) . then ( ( ) => {
50+ setCopiedId ( id ) ;
51+ timeout . current = setTimeout ( ( ) => setCopiedId ( null ) , 2000 ) ;
52+ } ) . catch ( ( ) => {
53+ // noop
54+ } ) ;
55+ } , [ ] ) ;
56+
57+ return { copiedId, handleCopyImport} ;
58+ }
59+
60+ function CopyInfoMessage ( ) {
61+ return (
62+ < div className = { style ( { display : 'flex' , alignItems : 'center' , justifyContent : 'center' , gap : 4 } ) } >
63+ < InfoCircle styles = { iconStyle ( { size : 'XS' } ) } />
64+ < span className = { style ( { font : 'ui' } ) } > Press an item to copy its import statement</ span >
65+ </ div >
66+ ) ;
67+ }
68+
69+ interface IconListBoxProps {
70+ items : typeof iconList ,
71+ copiedId : string | null ,
72+ onAction : ( item : string ) => void ,
73+ listBoxClassName ?: string
74+ }
75+
76+ function IconListBox ( { items, copiedId, onAction, listBoxClassName} : IconListBoxProps ) {
77+ return (
78+ < Virtualizer layout = { GridLayout } layoutOptions = { { minItemSize : new Size ( 64 , 64 ) , maxItemSize : new Size ( 64 , 64 ) , minSpace : new Size ( 12 , 12 ) , preserveAspectRatio : true } } >
79+ < ListBox
80+ onAction = { ( item ) => onAction ( item . toString ( ) ) }
81+ items = { items }
82+ layout = "grid"
83+ className = { listBoxClassName || style ( { width : '100%' , scrollPaddingY : 4 } ) }
84+ dependencies = { [ copiedId ] }
85+ renderEmptyState = { ( ) => (
86+ < IllustratedMessage styles = { style ( { marginX : 'auto' , marginY : 32 } ) } >
87+ < NoSearchResults />
88+ < Heading > No results</ Heading >
89+ < Content > Try a different search term.</ Content >
90+ </ IllustratedMessage >
91+ ) } >
92+ { item => < IconItem item = { item } isCopied = { copiedId === item . id } /> }
93+ </ ListBox >
94+ </ Virtualizer >
95+ ) ;
96+ }
97+
1998const itemStyle = style ( {
2099 ...focusRing ( ) ,
21100 size : 'full' ,
@@ -49,52 +128,12 @@ interface IconSearchViewProps {
49128}
50129
51130export function IconSearchView ( { filteredItems} : IconSearchViewProps ) {
52- let [ copiedId , setCopiedId ] = useState < string | null > ( null ) ;
53- let timeout = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
54-
55- useEffect ( ( ) => {
56- return ( ) => {
57- if ( timeout . current ) {
58- clearTimeout ( timeout . current ) ;
59- }
60- } ;
61- } , [ ] ) ;
62-
63- let handleCopyImport = useCallback ( ( id : string ) => {
64- if ( timeout . current ) {
65- clearTimeout ( timeout . current ) ;
66- }
67- navigator . clipboard . writeText ( `import ${ id } from '@react-spectrum/s2/icons/${ id } ';` ) . then ( ( ) => {
68- setCopiedId ( id ) ;
69- timeout . current = setTimeout ( ( ) => setCopiedId ( null ) , 2000 ) ;
70- } ) . catch ( ( ) => {
71- // noop
72- } ) ;
73- } , [ ] ) ;
131+ let { copiedId, handleCopyImport} = useCopyImport ( ) ;
74132
75133 return (
76134 < >
77- < div className = { style ( { display : 'flex' , alignItems : 'center' , justifyContent : 'center' , gap : 4 } ) } >
78- < InfoCircle styles = { iconStyle ( { size : 'XS' } ) } />
79- < span className = { style ( { font : 'ui' } ) } > Press an item to copy its import statement</ span >
80- </ div >
81- < Virtualizer layout = { GridLayout } layoutOptions = { { minItemSize : new Size ( 64 , 64 ) , maxItemSize : new Size ( 64 , 64 ) , minSpace : new Size ( 12 , 12 ) , preserveAspectRatio : true } } >
82- < ListBox
83- onAction = { ( item ) => handleCopyImport ( item . toString ( ) ) }
84- items = { filteredItems }
85- layout = "grid"
86- className = { style ( { width : '100%' , scrollPaddingY : 4 } ) }
87- dependencies = { [ copiedId ] }
88- renderEmptyState = { ( ) => (
89- < IllustratedMessage styles = { style ( { marginX : 'auto' , marginY : 32 } ) } >
90- < NoSearchResults />
91- < Heading > No results</ Heading >
92- < Content > Try a different search term.</ Content >
93- </ IllustratedMessage >
94- ) } >
95- { item => < IconItem item = { item } isCopied = { copiedId === item . id } /> }
96- </ ListBox >
97- </ Virtualizer >
135+ < CopyInfoMessage />
136+ < IconListBox items = { filteredItems } copiedId = { copiedId } onAction = { handleCopyImport } />
98137 </ >
99138 ) ;
100139}
@@ -187,68 +226,21 @@ export function IconSearchSkeleton() {
187226 ) ;
188227}
189228
190- export function IconCards ( ) {
191- let { contains} = useFilter ( { sensitivity : 'base' } ) ;
192- let filter = useCallback ( ( textValue , inputValue ) => {
193- // check if we're typing part of a category alias
194- for ( const alias of Object . keys ( iconAliases ) ) {
195- if ( contains ( alias , inputValue ) && iconAliases [ alias ] . includes ( textValue ) ) {
196- return true ;
197- }
198- }
199- // also compare for substrings in the icon's actual name
200- return textValue != null && contains ( textValue , inputValue ) ;
201- } , [ contains ] ) ;
202-
203- let [ copiedId , setCopiedId ] = useState < string | null > ( null ) ;
204- let timeout = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
205-
206- useEffect ( ( ) => {
207- return ( ) => {
208- if ( timeout . current ) {
209- clearTimeout ( timeout . current ) ;
210- }
211- } ;
212- } , [ ] ) ;
213-
214- let handleCopyImport = useCallback ( ( id : string ) => {
215- if ( timeout . current ) {
216- clearTimeout ( timeout . current ) ;
217- }
218- navigator . clipboard . writeText ( `import ${ id } from '@react-spectrum/s2/icons/${ id } ';` ) . then ( ( ) => {
219- setCopiedId ( id ) ;
220- timeout . current = setTimeout ( ( ) => setCopiedId ( null ) , 2000 ) ;
221- } ) . catch ( ( ) => {
222- // noop
223- } ) ;
224- } , [ ] ) ;
229+ export function IconsPageSearch ( ) {
230+ let filter = useIconFilter ( ) ;
231+ let { copiedId, handleCopyImport} = useCopyImport ( ) ;
225232
226233 return (
227234 < >
228235 < Autocomplete filter = { filter } >
229236 < div className = { style ( { display : 'flex' , flexDirection : 'column' , gap : 8 } ) } >
230237 < SearchField size = "L" aria-label = "Search icons" placeholder = "Search icons" />
231- < div className = { style ( { display : 'flex' , alignItems : 'center' , justifyContent : 'center' , gap : 4 } ) } >
232- < InfoCircle styles = { iconStyle ( { size : 'XS' } ) } />
233- < span className = { style ( { font : 'ui' } ) } > Press an item to copy its import statement</ span >
234- </ div >
235- < Virtualizer layout = { GridLayout } layoutOptions = { { minItemSize : new Size ( 64 , 64 ) , maxItemSize : new Size ( 64 , 64 ) , minSpace : new Size ( 12 , 12 ) , preserveAspectRatio : true } } >
236- < ListBox
237- onAction = { ( item ) => handleCopyImport ( item . toString ( ) ) }
238- items = { iconList }
239- layout = "grid"
240- className = { style ( { height : 440 , width : '100%' , maxHeight : '100%' , overflow : 'auto' , scrollPaddingY : 4 } ) }
241- dependencies = { [ copiedId ] }
242- renderEmptyState = { ( ) => (
243- < IllustratedMessage styles = { style ( { marginX : 'auto' , marginY : 32 } ) } >
244- < NoSearchResults />
245- < Heading > No results</ Heading >
246- < Content > Try a different search term.</ Content >
247- </ IllustratedMessage >
248- ) } >
249- { item => < IconItem item = { item } isCopied = { copiedId === item . id } /> }
250- </ ListBox >
251- </ Virtualizer >
238+ < CopyInfoMessage />
239+ < IconListBox
240+ items = { iconList }
241+ copiedId = { copiedId }
242+ onAction = { handleCopyImport }
243+ listBoxClassName = { style ( { height : 440 , width : '100%' , maxHeight : '100%' , overflow : 'auto' , scrollPaddingY : 4 } ) } />
252244 </ div >
253245 </ Autocomplete >
254246 </ >
0 commit comments