mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/archive_table' into archive_table
This commit is contained in:
		
						commit
						4ddf69436e
					
				| @ -78,7 +78,7 @@ | ||||
|     "lodash.clonedeep": "4.5.0", | ||||
|     "msw": "0.42.0", | ||||
|     "pkginfo": "^0.4.1", | ||||
|     "plausible-tracker": "^0.3.5", | ||||
|     "plausible-tracker": "0.3.7", | ||||
|     "prettier": "2.6.2", | ||||
|     "prop-types": "15.8.1", | ||||
|     "react": "17.0.2", | ||||
| @ -94,11 +94,11 @@ | ||||
|     "swr": "1.3.0", | ||||
|     "tss-react": "3.7.0", | ||||
|     "typescript": "4.7.3", | ||||
|     "vite": "2.9.9", | ||||
|     "vite": "2.9.10", | ||||
|     "vite-plugin-env-compatible": "^1.1.1", | ||||
|     "vite-plugin-svgr": "2.1.0", | ||||
|     "vite-tsconfig-paths": "3.5.0", | ||||
|     "vitest": "0.14.0", | ||||
|     "vitest": "0.14.1", | ||||
|     "whatwg-fetch": "^3.6.2" | ||||
|   }, | ||||
|   "jest": { | ||||
|  | ||||
| @ -14,7 +14,7 @@ interface ITableSearchProps { | ||||
| export const TableSearch: FC<ITableSearchProps> = ({ | ||||
|     initialValue, | ||||
|     onChange = () => {}, | ||||
|     placeholder = 'Search', | ||||
|     placeholder, | ||||
|     hasFilters, | ||||
|     getSearchContext, | ||||
| }) => { | ||||
| @ -30,7 +30,7 @@ export const TableSearch: FC<ITableSearchProps> = ({ | ||||
|         <TableSearchField | ||||
|             value={searchInputState!} | ||||
|             onChange={onSearchChange} | ||||
|             placeholder={`${placeholder}…`} | ||||
|             placeholder={placeholder} | ||||
|             hasFilters={hasFilters} | ||||
|             getSearchContext={getSearchContext} | ||||
|         /> | ||||
|  | ||||
| @ -1,17 +1,18 @@ | ||||
| import { useRef, useState } from 'react'; | ||||
| import { IconButton, InputBase, Tooltip } from '@mui/material'; | ||||
| import { Search, Close } from '@mui/icons-material'; | ||||
| import classnames from 'classnames'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useStyles } from './TableSearchField.styles'; | ||||
| import { TableSearchFieldSuggestions } from './TableSearchFieldSuggestions/TableSearchFieldSuggestions'; | ||||
| import { useState } from 'react'; | ||||
| import { IGetSearchContextOutput } from 'hooks/useSearch'; | ||||
| import { useKeyboardShortcut } from 'hooks/useKeyboardShortcut'; | ||||
| 
 | ||||
| interface ITableSearchFieldProps { | ||||
|     value: string; | ||||
|     onChange: (value: string) => void; | ||||
|     className?: string; | ||||
|     placeholder: string; | ||||
|     placeholder?: string; | ||||
|     hasFilters?: boolean; | ||||
|     getSearchContext?: () => IGetSearchContextOutput; | ||||
| } | ||||
| @ -20,12 +21,26 @@ export const TableSearchField = ({ | ||||
|     value = '', | ||||
|     onChange, | ||||
|     className, | ||||
|     placeholder, | ||||
|     placeholder: customPlaceholder, | ||||
|     hasFilters, | ||||
|     getSearchContext, | ||||
| }: ITableSearchFieldProps) => { | ||||
|     const ref = useRef<HTMLInputElement>(); | ||||
|     const { classes: styles } = useStyles(); | ||||
|     const [showSuggestions, setShowSuggestions] = useState(false); | ||||
|     const hotkey = useKeyboardShortcut( | ||||
|         { modifiers: ['ctrl'], key: 'k', preventDefault: true }, | ||||
|         () => { | ||||
|             ref.current?.focus(); | ||||
|             setShowSuggestions(true); | ||||
|         } | ||||
|     ); | ||||
|     useKeyboardShortcut({ key: 'Escape' }, () => { | ||||
|         if (document.activeElement === ref.current) { | ||||
|             setShowSuggestions(suggestions => !suggestions); | ||||
|         } | ||||
|     }); | ||||
|     const placeholder = `${customPlaceholder ?? 'Search'} (${hotkey})`; | ||||
| 
 | ||||
|     return ( | ||||
|         <div className={styles.container}> | ||||
| @ -40,6 +55,7 @@ export const TableSearchField = ({ | ||||
|                     className={classnames(styles.searchIcon, 'search-icon')} | ||||
|                 /> | ||||
|                 <InputBase | ||||
|                     inputRef={ref} | ||||
|                     placeholder={placeholder} | ||||
|                     classes={{ | ||||
|                         root: classnames(styles.inputRoot, 'input-container'), | ||||
| @ -64,6 +80,7 @@ export const TableSearchField = ({ | ||||
|                                     size="small" | ||||
|                                     onClick={() => { | ||||
|                                         onChange(''); | ||||
|                                         ref.current?.focus(); | ||||
|                                     }} | ||||
|                                 > | ||||
|                                     <Close className={styles.clearIcon} /> | ||||
|  | ||||
| @ -53,13 +53,13 @@ exports[`renders an empty list correctly 1`] = ` | ||||
|                     onClick={[Function]} | ||||
|                   > | ||||
|                     <input | ||||
|                       aria-label="Search…" | ||||
|                       aria-label="Search (Ctrl+K)" | ||||
|                       className="MuiInputBase-input mui-j79lc6-MuiInputBase-input" | ||||
|                       onAnimationStart={[Function]} | ||||
|                       onBlur={[Function]} | ||||
|                       onChange={[Function]} | ||||
|                       onFocus={[Function]} | ||||
|                       placeholder="Search…" | ||||
|                       placeholder="Search (Ctrl+K)" | ||||
|                       type="text" | ||||
|                       value="" | ||||
|                     /> | ||||
|  | ||||
							
								
								
									
										24
									
								
								frontend/src/hooks/useIsAppleDevice.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								frontend/src/hooks/useIsAppleDevice.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| import { useEffect, useState } from 'react'; | ||||
| 
 | ||||
| export const useIsAppleDevice = () => { | ||||
|     const [isAppleDevice, setIsAppleDevice] = useState<boolean>(); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         const platform = | ||||
|             ( | ||||
|                 navigator as unknown as { | ||||
|                     userAgentData: { platform: string }; | ||||
|                 } | ||||
|             )?.userAgentData?.platform || | ||||
|             navigator?.platform || | ||||
|             'unknown'; | ||||
| 
 | ||||
|         setIsAppleDevice( | ||||
|             platform.toLowerCase().includes('mac') || | ||||
|                 platform === 'iPhone' || | ||||
|                 platform === 'iPad' | ||||
|         ); | ||||
|     }, []); | ||||
| 
 | ||||
|     return isAppleDevice; | ||||
| }; | ||||
							
								
								
									
										76
									
								
								frontend/src/hooks/useKeyboardShortcut.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								frontend/src/hooks/useKeyboardShortcut.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| import { useEffect, useMemo } from 'react'; | ||||
| import { useIsAppleDevice } from './useIsAppleDevice'; | ||||
| 
 | ||||
| export const useKeyboardShortcut = ( | ||||
|     { | ||||
|         key, | ||||
|         modifiers = [], | ||||
|         preventDefault = false, | ||||
|     }: { | ||||
|         key: string; | ||||
|         modifiers?: Array<'ctrl' | 'alt' | 'shift'>; | ||||
|         preventDefault?: boolean; | ||||
|     }, | ||||
|     callback: () => void | ||||
| ) => { | ||||
|     const isAppleDevice = useIsAppleDevice(); | ||||
|     useEffect(() => { | ||||
|         const onKeyDown = (event: KeyboardEvent) => { | ||||
|             if (key !== event.key) { | ||||
|                 return; | ||||
|             } | ||||
|             if (modifiers.includes('ctrl')) { | ||||
|                 if (isAppleDevice) { | ||||
|                     if (!event.metaKey) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (!event.ctrlKey) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (modifiers.includes('alt') && !event.altKey) { | ||||
|                 return; | ||||
|             } | ||||
|             if (modifiers.includes('shift') && !event.shiftKey) { | ||||
|                 return; | ||||
|             } | ||||
|             if (preventDefault) { | ||||
|                 event.preventDefault(); | ||||
|             } | ||||
| 
 | ||||
|             callback(); | ||||
|         }; | ||||
| 
 | ||||
|         window.addEventListener('keydown', onKeyDown); | ||||
| 
 | ||||
|         return () => { | ||||
|             window.removeEventListener('keydown', onKeyDown); | ||||
|         }; | ||||
|     }, [isAppleDevice, key, modifiers, preventDefault, callback]); | ||||
| 
 | ||||
|     const formattedModifiers = useMemo( | ||||
|         () => | ||||
|             modifiers.map( | ||||
|                 modifier => | ||||
|                     ({ | ||||
|                         ctrl: isAppleDevice ? '⌘' : 'Ctrl', | ||||
|                         alt: 'Alt', | ||||
|                         shift: 'Shift', | ||||
|                     }[modifier]) | ||||
|             ), | ||||
|         [isAppleDevice, modifiers] | ||||
|     ); | ||||
| 
 | ||||
|     const hotkeyDescription = useMemo( | ||||
|         () => | ||||
|             [ | ||||
|                 ...formattedModifiers, | ||||
|                 `${key[0].toUpperCase()}${key.slice(1)}`, | ||||
|             ].join('+'), | ||||
|         [formattedModifiers, key] | ||||
|     ); | ||||
| 
 | ||||
|     return hotkeyDescription; | ||||
| }; | ||||
| @ -5041,10 +5041,10 @@ pkginfo@^0.4.1: | ||||
|   resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" | ||||
|   integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8= | ||||
| 
 | ||||
| plausible-tracker@^0.3.5: | ||||
|   version "0.3.5" | ||||
|   resolved "https://registry.yarnpkg.com/plausible-tracker/-/plausible-tracker-0.3.5.tgz#49c09a7eb727f1d5c859c3fc8072837b13ee9b85" | ||||
|   integrity sha512-6c6VPdPtI9KmIsfr8zLBViIDMt369eeaNA1J8JrAmAtrpSkeJWvjwcJ+cLn7gVJn5AtQWC8NgSEee2d/5RNytA== | ||||
| plausible-tracker@0.3.7: | ||||
|   version "0.3.7" | ||||
|   resolved "https://registry.yarnpkg.com/plausible-tracker/-/plausible-tracker-0.3.7.tgz#e93d241a1c46bf70f05317a6f177feff6c4865d5" | ||||
|   integrity sha512-yhM3VJekqMIEDbvx/PqratMyHpF4T/skTO4owzxSo3YMB/ZVmAYwh9c2iKRuJnkE3b4NwsMMW9b0Vw5VD5Gpyw== | ||||
| 
 | ||||
| postcss@^8.4.13: | ||||
|   version "8.4.13" | ||||
| @ -6028,7 +6028,19 @@ vite-tsconfig-paths@3.5.0: | ||||
|     recrawl-sync "^2.0.3" | ||||
|     tsconfig-paths "^4.0.0" | ||||
| 
 | ||||
| vite@2.9.9, vite@^2.9.9: | ||||
| vite@2.9.10: | ||||
|   version "2.9.10" | ||||
|   resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.10.tgz#f574d96655622c2e0fbc662edd0ed199c60fe91a" | ||||
|   integrity sha512-TwZRuSMYjpTurLqXspct+HZE7ONiW9d+wSWgvADGxhDPPyoIcNywY+RX4ng+QpK30DCa1l/oZgi2PLZDibhzbQ== | ||||
|   dependencies: | ||||
|     esbuild "^0.14.27" | ||||
|     postcss "^8.4.13" | ||||
|     resolve "^1.22.0" | ||||
|     rollup "^2.59.0" | ||||
|   optionalDependencies: | ||||
|     fsevents "~2.3.2" | ||||
| 
 | ||||
| vite@^2.9.9: | ||||
|   version "2.9.9" | ||||
|   resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.9.tgz#8b558987db5e60fedec2f4b003b73164cb081c5e" | ||||
|   integrity sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew== | ||||
| @ -6040,10 +6052,10 @@ vite@2.9.9, vite@^2.9.9: | ||||
|   optionalDependencies: | ||||
|     fsevents "~2.3.2" | ||||
| 
 | ||||
| vitest@0.14.0: | ||||
|   version "0.14.0" | ||||
|   resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.14.0.tgz#8fe92608d5379972b8f5a2424187fc4a3e453cd5" | ||||
|   integrity sha512-3Ns7c6TindS6OmweY4dU7sWT7g/YJBenwsMdUMEO/oS81bzoCEaA58vlg/9U1WRjrNFwQYsuyISehAvCKwoMZQ== | ||||
| vitest@0.14.1: | ||||
|   version "0.14.1" | ||||
|   resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.14.1.tgz#f2fd8b31abdbbadb9ee895f8fde35a068ea2a5f5" | ||||
|   integrity sha512-2UUm6jYgkwh7Y3VKSRR8OuaNCm+iA5LPDnal7jyITN39maZK9L+JVxqjtQ39PSFo5Fl3/BgaJvER6GGHX9JLxg== | ||||
|   dependencies: | ||||
|     "@types/chai" "^4.3.1" | ||||
|     "@types/chai-subset" "^1.3.3" | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user