mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02: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",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"msw": "0.42.0",
|
"msw": "0.42.0",
|
||||||
"pkginfo": "^0.4.1",
|
"pkginfo": "^0.4.1",
|
||||||
"plausible-tracker": "^0.3.5",
|
"plausible-tracker": "0.3.7",
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
@ -94,11 +94,11 @@
|
|||||||
"swr": "1.3.0",
|
"swr": "1.3.0",
|
||||||
"tss-react": "3.7.0",
|
"tss-react": "3.7.0",
|
||||||
"typescript": "4.7.3",
|
"typescript": "4.7.3",
|
||||||
"vite": "2.9.9",
|
"vite": "2.9.10",
|
||||||
"vite-plugin-env-compatible": "^1.1.1",
|
"vite-plugin-env-compatible": "^1.1.1",
|
||||||
"vite-plugin-svgr": "2.1.0",
|
"vite-plugin-svgr": "2.1.0",
|
||||||
"vite-tsconfig-paths": "3.5.0",
|
"vite-tsconfig-paths": "3.5.0",
|
||||||
"vitest": "0.14.0",
|
"vitest": "0.14.1",
|
||||||
"whatwg-fetch": "^3.6.2"
|
"whatwg-fetch": "^3.6.2"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
@ -14,7 +14,7 @@ interface ITableSearchProps {
|
|||||||
export const TableSearch: FC<ITableSearchProps> = ({
|
export const TableSearch: FC<ITableSearchProps> = ({
|
||||||
initialValue,
|
initialValue,
|
||||||
onChange = () => {},
|
onChange = () => {},
|
||||||
placeholder = 'Search',
|
placeholder,
|
||||||
hasFilters,
|
hasFilters,
|
||||||
getSearchContext,
|
getSearchContext,
|
||||||
}) => {
|
}) => {
|
||||||
@ -30,7 +30,7 @@ export const TableSearch: FC<ITableSearchProps> = ({
|
|||||||
<TableSearchField
|
<TableSearchField
|
||||||
value={searchInputState!}
|
value={searchInputState!}
|
||||||
onChange={onSearchChange}
|
onChange={onSearchChange}
|
||||||
placeholder={`${placeholder}…`}
|
placeholder={placeholder}
|
||||||
hasFilters={hasFilters}
|
hasFilters={hasFilters}
|
||||||
getSearchContext={getSearchContext}
|
getSearchContext={getSearchContext}
|
||||||
/>
|
/>
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
|
import { useRef, useState } from 'react';
|
||||||
import { IconButton, InputBase, Tooltip } from '@mui/material';
|
import { IconButton, InputBase, Tooltip } from '@mui/material';
|
||||||
import { Search, Close } from '@mui/icons-material';
|
import { Search, Close } from '@mui/icons-material';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { useStyles } from './TableSearchField.styles';
|
import { useStyles } from './TableSearchField.styles';
|
||||||
import { TableSearchFieldSuggestions } from './TableSearchFieldSuggestions/TableSearchFieldSuggestions';
|
import { TableSearchFieldSuggestions } from './TableSearchFieldSuggestions/TableSearchFieldSuggestions';
|
||||||
import { useState } from 'react';
|
|
||||||
import { IGetSearchContextOutput } from 'hooks/useSearch';
|
import { IGetSearchContextOutput } from 'hooks/useSearch';
|
||||||
|
import { useKeyboardShortcut } from 'hooks/useKeyboardShortcut';
|
||||||
|
|
||||||
interface ITableSearchFieldProps {
|
interface ITableSearchFieldProps {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
placeholder: string;
|
placeholder?: string;
|
||||||
hasFilters?: boolean;
|
hasFilters?: boolean;
|
||||||
getSearchContext?: () => IGetSearchContextOutput;
|
getSearchContext?: () => IGetSearchContextOutput;
|
||||||
}
|
}
|
||||||
@ -20,12 +21,26 @@ export const TableSearchField = ({
|
|||||||
value = '',
|
value = '',
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
placeholder,
|
placeholder: customPlaceholder,
|
||||||
hasFilters,
|
hasFilters,
|
||||||
getSearchContext,
|
getSearchContext,
|
||||||
}: ITableSearchFieldProps) => {
|
}: ITableSearchFieldProps) => {
|
||||||
|
const ref = useRef<HTMLInputElement>();
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
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 (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@ -40,6 +55,7 @@ export const TableSearchField = ({
|
|||||||
className={classnames(styles.searchIcon, 'search-icon')}
|
className={classnames(styles.searchIcon, 'search-icon')}
|
||||||
/>
|
/>
|
||||||
<InputBase
|
<InputBase
|
||||||
|
inputRef={ref}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
classes={{
|
classes={{
|
||||||
root: classnames(styles.inputRoot, 'input-container'),
|
root: classnames(styles.inputRoot, 'input-container'),
|
||||||
@ -64,6 +80,7 @@ export const TableSearchField = ({
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onChange('');
|
onChange('');
|
||||||
|
ref.current?.focus();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Close className={styles.clearIcon} />
|
<Close className={styles.clearIcon} />
|
||||||
|
@ -53,13 +53,13 @@ exports[`renders an empty list correctly 1`] = `
|
|||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label="Search…"
|
aria-label="Search (Ctrl+K)"
|
||||||
className="MuiInputBase-input mui-j79lc6-MuiInputBase-input"
|
className="MuiInputBase-input mui-j79lc6-MuiInputBase-input"
|
||||||
onAnimationStart={[Function]}
|
onAnimationStart={[Function]}
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onFocus={[Function]}
|
onFocus={[Function]}
|
||||||
placeholder="Search…"
|
placeholder="Search (Ctrl+K)"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
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"
|
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff"
|
||||||
integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=
|
integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=
|
||||||
|
|
||||||
plausible-tracker@^0.3.5:
|
plausible-tracker@0.3.7:
|
||||||
version "0.3.5"
|
version "0.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/plausible-tracker/-/plausible-tracker-0.3.5.tgz#49c09a7eb727f1d5c859c3fc8072837b13ee9b85"
|
resolved "https://registry.yarnpkg.com/plausible-tracker/-/plausible-tracker-0.3.7.tgz#e93d241a1c46bf70f05317a6f177feff6c4865d5"
|
||||||
integrity sha512-6c6VPdPtI9KmIsfr8zLBViIDMt369eeaNA1J8JrAmAtrpSkeJWvjwcJ+cLn7gVJn5AtQWC8NgSEee2d/5RNytA==
|
integrity sha512-yhM3VJekqMIEDbvx/PqratMyHpF4T/skTO4owzxSo3YMB/ZVmAYwh9c2iKRuJnkE3b4NwsMMW9b0Vw5VD5Gpyw==
|
||||||
|
|
||||||
postcss@^8.4.13:
|
postcss@^8.4.13:
|
||||||
version "8.4.13"
|
version "8.4.13"
|
||||||
@ -6028,7 +6028,19 @@ vite-tsconfig-paths@3.5.0:
|
|||||||
recrawl-sync "^2.0.3"
|
recrawl-sync "^2.0.3"
|
||||||
tsconfig-paths "^4.0.0"
|
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"
|
version "2.9.9"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.9.tgz#8b558987db5e60fedec2f4b003b73164cb081c5e"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.9.tgz#8b558987db5e60fedec2f4b003b73164cb081c5e"
|
||||||
integrity sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==
|
integrity sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==
|
||||||
@ -6040,10 +6052,10 @@ vite@2.9.9, vite@^2.9.9:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
vitest@0.14.0:
|
vitest@0.14.1:
|
||||||
version "0.14.0"
|
version "0.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.14.0.tgz#8fe92608d5379972b8f5a2424187fc4a3e453cd5"
|
resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.14.1.tgz#f2fd8b31abdbbadb9ee895f8fde35a068ea2a5f5"
|
||||||
integrity sha512-3Ns7c6TindS6OmweY4dU7sWT7g/YJBenwsMdUMEO/oS81bzoCEaA58vlg/9U1WRjrNFwQYsuyISehAvCKwoMZQ==
|
integrity sha512-2UUm6jYgkwh7Y3VKSRR8OuaNCm+iA5LPDnal7jyITN39maZK9L+JVxqjtQ39PSFo5Fl3/BgaJvER6GGHX9JLxg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/chai" "^4.3.1"
|
"@types/chai" "^4.3.1"
|
||||||
"@types/chai-subset" "^1.3.3"
|
"@types/chai-subset" "^1.3.3"
|
||||||
|
Loading…
Reference in New Issue
Block a user