diff --git a/frontend/src/component/application/ApplicationList/ApplicationList.tsx b/frontend/src/component/application/ApplicationList/ApplicationList.tsx index db6c5416f3..972743e70c 100644 --- a/frontend/src/component/application/ApplicationList/ApplicationList.tsx +++ b/frontend/src/component/application/ApplicationList/ApplicationList.tsx @@ -1,9 +1,8 @@ import { useMemo, useState } from 'react'; import { CircularProgress } from '@material-ui/core'; import { Warning } from '@material-ui/icons'; - import { AppsLinkList, styles as commonStyles } from '../../common'; -import SearchField from '../../common/SearchField/SearchField'; +import { SearchField } from 'component/common/SearchField/SearchField'; import PageContent from '../../common/PageContent/PageContent'; import HeaderTitle from '../../common/HeaderTitle'; import useApplications from '../../../hooks/api/getters/useApplications/useApplications'; diff --git a/frontend/src/component/common/SearchField/SearchField.jsx b/frontend/src/component/common/SearchField/SearchField.jsx deleted file mode 100644 index 87f7980e69..0000000000 --- a/frontend/src/component/common/SearchField/SearchField.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useState } from 'react'; -import classnames from 'classnames'; -import PropTypes from 'prop-types'; -import { debounce } from 'debounce'; -import { InputBase } from '@material-ui/core'; -import SearchIcon from '@material-ui/icons/Search'; - -import { useStyles } from './styles'; - -function SearchField({ initialValue = '', updateValue, className = '' }) { - const styles = useStyles(); - - const [localValue, setLocalValue] = useState(initialValue); - const debounceUpdateValue = debounce(updateValue, 500); - - const handleChange = e => { - e.preventDefault(); - const v = e.target.value || ''; - setLocalValue(v); - debounceUpdateValue(v); - }; - - const handleKeyPress = e => { - if (e.key === 'Enter') { - updateValue(localValue); - } - }; - - const updateNow = () => { - updateValue(localValue); - }; - - return ( -
-
- - -
-
- ); -} - -SearchField.propTypes = { - value: PropTypes.string, - updateValue: PropTypes.func.isRequired, -}; - -export default SearchField; diff --git a/frontend/src/component/common/SearchField/SearchField.tsx b/frontend/src/component/common/SearchField/SearchField.tsx new file mode 100644 index 0000000000..333cc5a603 --- /dev/null +++ b/frontend/src/component/common/SearchField/SearchField.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import classnames from 'classnames'; +import { debounce } from 'debounce'; +import { InputBase, Chip } from '@material-ui/core'; +import SearchIcon from '@material-ui/icons/Search'; +import { useStyles } from './styles'; +import ConditionallyRender from 'component/common/ConditionallyRender'; + +interface ISearchFieldProps { + updateValue: React.Dispatch>; + initialValue?: string; + className?: string; + showValueChip?: boolean; +} + +export const SearchField = ({ + updateValue, + initialValue = '', + className = '', + showValueChip, +}: ISearchFieldProps) => { + const styles = useStyles(); + const [localValue, setLocalValue] = useState(initialValue); + const debounceUpdateValue = debounce(updateValue, 500); + + const handleChange = (event: React.ChangeEvent) => { + event.preventDefault(); + const value = event.target.value || ''; + setLocalValue(value); + debounceUpdateValue(value); + }; + + const handleKeyPress = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + updateValue(localValue); + } + }; + + const updateNow = () => { + updateValue(localValue); + }; + + const onDelete = () => { + setLocalValue(''); + updateValue(''); + }; + + return ( +
+
+ + +
+ + } + /> +
+ ); +}; diff --git a/frontend/src/component/common/SearchField/styles.js b/frontend/src/component/common/SearchField/styles.js index 09a4e41d1f..ed7c9e480a 100644 --- a/frontend/src/component/common/SearchField/styles.js +++ b/frontend/src/component/common/SearchField/styles.js @@ -1,6 +1,12 @@ import { makeStyles } from '@material-ui/styles'; export const useStyles = makeStyles(theme => ({ + container: { + display: 'flex', + alignItems: 'center', + flexWrap: 'wrap', + gap: '1rem', + }, search: { display: 'flex', alignItems: 'center', @@ -8,9 +14,6 @@ export const useStyles = makeStyles(theme => ({ borderRadius: '25px', padding: '0.25rem 0.5rem', maxWidth: '450px', - [theme.breakpoints.down('sm')]: { - margin: '0 auto', - }, [theme.breakpoints.down('xs')]: { width: '100%', }, diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx index 7c5ea29409..a356fe3015 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx @@ -5,20 +5,15 @@ import { Link } from 'react-router-dom'; import { Button, IconButton, List, ListItem, Tooltip } from '@material-ui/core'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import { Add } from '@material-ui/icons'; - import FeatureToggleListItem from './FeatureToggleListItem'; -import SearchField from '../../common/SearchField/SearchField'; +import { SearchField } from '../../common/SearchField/SearchField'; import FeatureToggleListActions from './FeatureToggleListActions'; import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; import PageContent from '../../common/PageContent/PageContent'; import HeaderTitle from '../../common/HeaderTitle'; - import loadingFeatures from './loadingFeatures'; - import { CREATE_FEATURE } from '../../providers/AccessProvider/permissions'; - import AccessContext from '../../../contexts/AccessContext'; - import { useStyles } from './styles'; import ListPlaceholder from '../../common/ListPlaceholder/ListPlaceholder'; import { getCreateTogglePath } from '../../../utils/route-path-helpers'; @@ -101,7 +96,11 @@ const FeatureToggleList = ({ ); }; - const headerTitle = archive ? 'Archived Features' : 'Features'; + const headerTitle = filter.query + ? 'Search results' + : archive + ? 'Archived Features' + : 'Features'; return (
@@ -109,6 +108,7 @@ const FeatureToggleList = ({ -
+
@@ -20,7 +22,7 @@ exports[`renders correctly with one feature 1`] = ` />
@@ -31,7 +33,7 @@ exports[`renders correctly with one feature 1`] = ` onBlur={[Function]} onChange={[Function]} onFocus={[Function]} - placeholder="Search…" + placeholder="Search..." type="text" value="" /> @@ -55,29 +57,29 @@ exports[`renders correctly with one feature 1`] = ` } >

Features

    -
    +
    @@ -212,7 +216,7 @@ exports[`renders correctly with one feature without permissions 1`] = ` />
    @@ -223,7 +227,7 @@ exports[`renders correctly with one feature without permissions 1`] = ` onBlur={[Function]} onChange={[Function]} onFocus={[Function]} - placeholder="Search…" + placeholder="Search..." type="text" value="" /> @@ -247,29 +251,29 @@ exports[`renders correctly with one feature without permissions 1`] = ` } >

    Features

      ({ searchBarContainer: { marginBottom: '2rem', display: 'flex', + gap: '1rem', justifyContent: 'space-between', alignItems: 'center', [theme.breakpoints.down('xs')]: { diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx index 38fc7d70a6..0c8afb086c 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx @@ -15,7 +15,6 @@ import { Tooltip } from '@material-ui/core'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; import { useStyles } from './FeatureStrategyEditable.styles'; import { Delete } from '@material-ui/icons'; -import { PRODUCTION } from '../../../../../../constants/environmentTypes'; import { DELETE_STRATEGY_ID, STRATEGY_ACCORDION_ID, diff --git a/frontend/src/hooks/useEventSettings.ts b/frontend/src/hooks/useEventSettings.ts index 21478a8bc6..d7c7c2ea95 100644 --- a/frontend/src/hooks/useEventSettings.ts +++ b/frontend/src/hooks/useEventSettings.ts @@ -1,5 +1,5 @@ import { getBasePath } from '../utils/format-path'; -import { createPersistentGlobalState } from './usePersistentGlobalState'; +import { createPersistentGlobalStateHook } from './usePersistentGlobalState'; import React from 'react'; export interface IEventSettings { @@ -21,7 +21,7 @@ const createInitialValue = (): IEventSettings => { return { showData: false }; }; -const useGlobalState = createPersistentGlobalState( +const useGlobalState = createPersistentGlobalStateHook( `${getBasePath()}:useEventSettings:v1`, createInitialValue() ); diff --git a/frontend/src/hooks/useFeaturesFilter.ts b/frontend/src/hooks/useFeaturesFilter.ts index 452f73911a..085d13622b 100644 --- a/frontend/src/hooks/useFeaturesFilter.ts +++ b/frontend/src/hooks/useFeaturesFilter.ts @@ -1,7 +1,6 @@ -import { IFeatureToggle } from '../interfaces/featureToggle'; +import { IFeatureToggle } from 'interfaces/featureToggle'; import React, { useMemo } from 'react'; -import { getBasePath } from '../utils/format-path'; -import { createPersistentGlobalState } from './usePersistentGlobalState'; +import { createGlobalStateHook } from 'hooks/useGlobalState'; export interface IFeaturesFilter { query?: string; @@ -16,8 +15,8 @@ export interface IFeaturesSortOutput { // Store the features filter state globally, and in localStorage. // When changing the format of IFeaturesFilter, change the version as well. -const useFeaturesFilterState = createPersistentGlobalState( - `${getBasePath()}:useFeaturesFilter:v1`, +const useFeaturesFilterState = createGlobalStateHook( + 'useFeaturesFilterState', { project: '*' } ); diff --git a/frontend/src/hooks/useFeaturesSort.ts b/frontend/src/hooks/useFeaturesSort.ts index 282916671a..a72c2fc295 100644 --- a/frontend/src/hooks/useFeaturesSort.ts +++ b/frontend/src/hooks/useFeaturesSort.ts @@ -1,7 +1,7 @@ import { IFeatureToggle } from '../interfaces/featureToggle'; import React, { useMemo } from 'react'; import { getBasePath } from '../utils/format-path'; -import { createPersistentGlobalState } from './usePersistentGlobalState'; +import { createPersistentGlobalStateHook } from './usePersistentGlobalState'; type FeaturesSortType = | 'name' @@ -29,7 +29,7 @@ export interface IFeaturesFilterSortOption { // Store the features sort state globally, and in localStorage. // When changing the format of IFeaturesSort, change the version as well. -const useFeaturesSortState = createPersistentGlobalState( +const useFeaturesSortState = createPersistentGlobalStateHook( `${getBasePath()}:useFeaturesSort:v1`, { type: 'name' } ); diff --git a/frontend/src/hooks/useGlobalState.ts b/frontend/src/hooks/useGlobalState.ts new file mode 100644 index 0000000000..37469ebd6a --- /dev/null +++ b/frontend/src/hooks/useGlobalState.ts @@ -0,0 +1,23 @@ +import React from 'react'; +import { createGlobalState } from 'react-hooks-global-state'; + +type UseGlobalState = () => [ + value: T, + setValue: React.Dispatch> +]; + +// Create a hook that stores global state (shared across all hook instances). +export const createGlobalStateHook = ( + key: string, + initialValue: T +): UseGlobalState => { + const container = createGlobalState<{ [key: string]: T }>({ + [key]: initialValue, + }); + + const setGlobalState = (value: React.SetStateAction) => { + container.setGlobalState(key, value); + }; + + return () => [container.useGlobalState(key)[0], setGlobalState]; +}; diff --git a/frontend/src/hooks/useLocationSettings.ts b/frontend/src/hooks/useLocationSettings.ts index 5ffa2d18de..5e74ff0240 100644 --- a/frontend/src/hooks/useLocationSettings.ts +++ b/frontend/src/hooks/useLocationSettings.ts @@ -1,5 +1,5 @@ import { getBasePath } from '../utils/format-path'; -import { createPersistentGlobalState } from './usePersistentGlobalState'; +import { createPersistentGlobalStateHook } from './usePersistentGlobalState'; import React from 'react'; export interface ILocationSettings { @@ -23,7 +23,7 @@ const createInitialValue = (): ILocationSettings => { return { locale: navigator.language }; }; -const useGlobalState = createPersistentGlobalState( +const useGlobalState = createPersistentGlobalStateHook( `${getBasePath()}:useLocationSettings:v1`, createInitialValue() ); diff --git a/frontend/src/hooks/usePersistentGlobalState.ts b/frontend/src/hooks/usePersistentGlobalState.ts index 1a06da3ffe..1f743a23ea 100644 --- a/frontend/src/hooks/usePersistentGlobalState.ts +++ b/frontend/src/hooks/usePersistentGlobalState.ts @@ -10,7 +10,7 @@ type UsePersistentGlobalState = () => [ // Create a hook that stores global state (shared across all hook instances). // The state is also persisted to localStorage and restored on page load. // The localStorage state is not synced between tabs. -export const createPersistentGlobalState = ( +export const createPersistentGlobalStateHook = ( key: string, initialValue: T ): UsePersistentGlobalState => {