mirror of
https://github.com/strapi/strapi.git
synced 2025-11-24 22:21:38 +00:00
Merge pull request #11480 from strapi/v4/search-url-query
V4/ Search url query
This commit is contained in:
commit
234dd5a76c
@ -12,7 +12,7 @@ import { stringify } from 'qs';
|
|||||||
import {
|
import {
|
||||||
NoPermissions,
|
NoPermissions,
|
||||||
CheckPermissions,
|
CheckPermissions,
|
||||||
Search,
|
SearchURLQuery,
|
||||||
useFocusWhenNavigate,
|
useFocusWhenNavigate,
|
||||||
useQueryParams,
|
useQueryParams,
|
||||||
useNotification,
|
useNotification,
|
||||||
@ -296,7 +296,7 @@ function ListView({
|
|||||||
startActions={
|
startActions={
|
||||||
<>
|
<>
|
||||||
{isSearchable && (
|
{isSearchable && (
|
||||||
<Search
|
<SearchURLQuery
|
||||||
label={formatMessage(
|
label={formatMessage(
|
||||||
{ id: 'app.component.search.label', defaultMessage: 'Search for {target}' },
|
{ id: 'app.component.search.label', defaultMessage: 'Search for {target}' },
|
||||||
{ target: headerLayoutTitle }
|
{ target: headerLayoutTitle }
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
DynamicTable,
|
DynamicTable,
|
||||||
Search,
|
SearchURLQuery,
|
||||||
SettingsPageTitle,
|
SettingsPageTitle,
|
||||||
useRBAC,
|
useRBAC,
|
||||||
useNotification,
|
useNotification,
|
||||||
@ -129,7 +129,7 @@ const ListPage = () => {
|
|||||||
<ActionLayout
|
<ActionLayout
|
||||||
startActions={
|
startActions={
|
||||||
<>
|
<>
|
||||||
<Search
|
<SearchURLQuery
|
||||||
label={formatMessage(
|
label={formatMessage(
|
||||||
{ id: 'app.component.search.label', defaultMessage: 'Search for {target}' },
|
{ id: 'app.component.search.label', defaultMessage: 'Search for {target}' },
|
||||||
{ target: title }
|
{ target: title }
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useReducer, useState } from 'react';
|
|||||||
import {
|
import {
|
||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
LoadingIndicatorPage,
|
LoadingIndicatorPage,
|
||||||
Search,
|
SearchURLQuery,
|
||||||
SettingsPageTitle,
|
SettingsPageTitle,
|
||||||
useNotification,
|
useNotification,
|
||||||
useQueryParams,
|
useQueryParams,
|
||||||
@ -277,7 +277,7 @@ const RoleListPage = () => {
|
|||||||
{canRead && (
|
{canRead && (
|
||||||
<ActionLayout
|
<ActionLayout
|
||||||
startActions={
|
startActions={
|
||||||
<Search
|
<SearchURLQuery
|
||||||
label={formatMessage(
|
label={formatMessage(
|
||||||
{ id: 'app.component.search.label', defaultMessage: 'Search for {target}' },
|
{ id: 'app.component.search.label', defaultMessage: 'Search for {target}' },
|
||||||
{ target: title }
|
{ target: title }
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
<!--- Search.stories.mdx --->
|
|
||||||
|
|
||||||
import { ArgsTable, Meta } from '@storybook/addon-docs';
|
|
||||||
import Search from './index';
|
|
||||||
|
|
||||||
<Meta title="components/Search" />
|
|
||||||
|
|
||||||
# Search
|
|
||||||
|
|
||||||
This component provides and input to search an array
|
|
||||||
|
|
||||||
## Imports
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { Search } from '@strapi/helper-plugin';
|
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
import { Search, useQueryParams } from '@strapi/helper-plugin';
|
|
||||||
import matchSorter from 'match-sorter';
|
|
||||||
|
|
||||||
const HomePage = () => {
|
|
||||||
const [{ query }] = useQueryParams()
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const _q = query?._q || ''
|
|
||||||
const items = [{name: 'Paul', instrument: 'bass'}, {name: 'George', instrument: 'guitar'}]
|
|
||||||
|
|
||||||
const sortedList = matchSorter(items, _q, {keys: ['name', 'instrument']})
|
|
||||||
const itemsList = sortedList?.length ? sortedList : items
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Search
|
|
||||||
label={formatMessage({
|
|
||||||
id: 'app.component.search.label',
|
|
||||||
defaultMessage: 'Search for {target}' },
|
|
||||||
{ target: 'users' }
|
|
||||||
)}
|
|
||||||
// Use this props to send an event
|
|
||||||
trackedEvent="didSearch"
|
|
||||||
/>
|
|
||||||
{itemsList.map(item => (
|
|
||||||
<div>
|
|
||||||
<h1>{item.name}</h1>
|
|
||||||
<p>{item.instrument}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
)
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
<ArgsTable of={Search} />
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import SearchIcon from '@strapi/icons/Search';
|
|
||||||
import { Searchbar } from '@strapi/design-system/Searchbar';
|
|
||||||
import { IconButton } from '@strapi/design-system/IconButton';
|
|
||||||
import useQueryParams from '../../hooks/useQueryParams';
|
|
||||||
import useTracking from '../../hooks/useTracking';
|
|
||||||
|
|
||||||
const Search = ({ label, trackedEvent }) => {
|
|
||||||
const wrapperRef = useRef(null);
|
|
||||||
const iconButtonRef = useRef(null);
|
|
||||||
const isMountedRef = useRef(false);
|
|
||||||
const [didSearch, setDidSearch] = useState(false);
|
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [{ query }, setQuery] = useQueryParams();
|
|
||||||
const [value, setValue] = useState(query?._q || '');
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const { trackUsage } = useTracking();
|
|
||||||
|
|
||||||
const handleToggle = () => setIsOpen(prev => !prev);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isMountedRef.current) {
|
|
||||||
if (isOpen) {
|
|
||||||
wrapperRef.current.querySelector('input').focus();
|
|
||||||
} else {
|
|
||||||
iconButtonRef.current.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isMountedRef.current = true;
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (didSearch && trackedEvent) {
|
|
||||||
trackUsage(trackedEvent);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [didSearch, trackedEvent]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handler = setTimeout(() => {
|
|
||||||
if (!didSearch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
setQuery({ _q: value, page: 1 });
|
|
||||||
} else {
|
|
||||||
setDidSearch(false);
|
|
||||||
setQuery({ _q: '' }, 'remove');
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
return () => clearTimeout(handler);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (value && !isOpen) {
|
|
||||||
setIsOpen(true);
|
|
||||||
}
|
|
||||||
}, [value, isOpen]);
|
|
||||||
|
|
||||||
if (isOpen) {
|
|
||||||
return (
|
|
||||||
<div ref={wrapperRef}>
|
|
||||||
<Searchbar
|
|
||||||
name="search"
|
|
||||||
onChange={({ target: { value } }) => {
|
|
||||||
setDidSearch(true);
|
|
||||||
setValue(value);
|
|
||||||
}}
|
|
||||||
value={value}
|
|
||||||
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
|
|
||||||
onClear={() => {
|
|
||||||
setValue('');
|
|
||||||
setIsOpen(false);
|
|
||||||
setDidSearch(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Searchbar>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IconButton ref={iconButtonRef} icon={<SearchIcon />} label="Search" onClick={handleToggle} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Search.defaultProps = {
|
|
||||||
trackedEvent: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
Search.propTypes = {
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
trackedEvent: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Search;
|
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
<!--- Search.stories.mdx --->
|
||||||
|
|
||||||
|
import { ArgsTable, Meta, Canvas, Story } from '@storybook/addon-docs';
|
||||||
|
import { Stack } from '@strapi/design-system/Stack';
|
||||||
|
import matchSorter from 'match-sorter';
|
||||||
|
import useQueryParams from '../../hooks/useQueryParams';
|
||||||
|
import SearchURLQuery from './index';
|
||||||
|
|
||||||
|
<Meta title="components/SearchURLQuery" />
|
||||||
|
|
||||||
|
# SearchURLQuery
|
||||||
|
|
||||||
|
This component provides and input to search an array
|
||||||
|
|
||||||
|
## Imports
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { SearchURLQuery } from '@strapi/helper-plugin';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { SearchURLQuery, useQueryParams } from '@strapi/helper-plugin';
|
||||||
|
import matchSorter from 'match-sorter';
|
||||||
|
|
||||||
|
const HomePage = () => {
|
||||||
|
const [{ query }] = useQueryParams()
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const _q = query?._q || ''
|
||||||
|
const items = [{name: 'Paul', instrument: 'bass'}, {name: 'George', instrument: 'guitar'}]
|
||||||
|
|
||||||
|
const sortedList = matchSorter(items, _q, {keys: ['name', 'instrument']})
|
||||||
|
const itemsList = sortedList?.length ? sortedList : items
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SearchURLQuery
|
||||||
|
label={formatMessage({
|
||||||
|
id: 'app.component.search.label',
|
||||||
|
defaultMessage: 'Search for {target}' },
|
||||||
|
{ target: 'users' }
|
||||||
|
)}
|
||||||
|
// Use this props to send an event
|
||||||
|
trackedEvent="didSearch"
|
||||||
|
/>
|
||||||
|
{itemsList.map(item => (
|
||||||
|
<div>
|
||||||
|
<h1>{item.name}</h1>
|
||||||
|
<p>{item.instrument}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Base
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story name="base">
|
||||||
|
{() => {
|
||||||
|
const [{ query }] = useQueryParams()
|
||||||
|
const _q = query?._q || ''
|
||||||
|
const items = [{name: 'Paul', instrument: 'bass'}, {name: 'George', instrument: 'guitar'}]
|
||||||
|
const sortedList = matchSorter(items, _q, {keys: ['name', 'instrument']})
|
||||||
|
const itemsList = sortedList?.length ? sortedList : items;
|
||||||
|
return (
|
||||||
|
<Stack paddingTop={6} size={4}>
|
||||||
|
<SearchURLQuery label="Label" />
|
||||||
|
{itemsList.map((item, i) => (
|
||||||
|
<div key={i}>
|
||||||
|
<h1>{item.name}</h1>
|
||||||
|
<p>{item.instrument}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
<ArgsTable of={SearchURLQuery} />
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
import React, { useLayoutEffect, useState, useRef } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import SearchIcon from '@strapi/icons/Search';
|
||||||
|
import { Searchbar, SearchForm } from '@strapi/design-system/Searchbar';
|
||||||
|
import { IconButton } from '@strapi/design-system/IconButton';
|
||||||
|
import useQueryParams from '../../hooks/useQueryParams';
|
||||||
|
import useTracking from '../../hooks/useTracking';
|
||||||
|
|
||||||
|
const SearchURLQuery = ({ label, trackedEvent }) => {
|
||||||
|
const wrapperRef = useRef(null);
|
||||||
|
const iconButtonRef = useRef(null);
|
||||||
|
|
||||||
|
const [{ query }, setQuery] = useQueryParams();
|
||||||
|
const [value, setValue] = useState(query?._q || '');
|
||||||
|
const [isOpen, setIsOpen] = useState(!!value);
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const { trackUsage } = useTracking();
|
||||||
|
|
||||||
|
const handleToggle = () => setIsOpen(prev => !prev);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
setTimeout(() => {
|
||||||
|
wrapperRef.current.querySelector('input').focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
setValue('');
|
||||||
|
setQuery({ _q: '' }, 'remove');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
if (trackedEvent) {
|
||||||
|
trackUsage(trackedEvent);
|
||||||
|
}
|
||||||
|
setQuery({ _q: value, page: 1 });
|
||||||
|
} else {
|
||||||
|
handleToggle();
|
||||||
|
setQuery({ _q: '' }, 'remove');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
return (
|
||||||
|
<div ref={wrapperRef}>
|
||||||
|
<SearchForm onSubmit={handleSubmit}>
|
||||||
|
<Searchbar
|
||||||
|
name="search"
|
||||||
|
onChange={({ target: { value } }) => setValue(value)}
|
||||||
|
value={value}
|
||||||
|
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
|
||||||
|
onClear={handleClear}
|
||||||
|
size="S"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Searchbar>
|
||||||
|
</SearchForm>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton ref={iconButtonRef} icon={<SearchIcon />} label="Search" onClick={handleToggle} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchURLQuery.defaultProps = {
|
||||||
|
trackedEvent: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchURLQuery.propTypes = {
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
trackedEvent: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchURLQuery;
|
||||||
@ -0,0 +1,495 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Tests for SearchURLQuery
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||||
|
import SearchURLQuery from '../index';
|
||||||
|
|
||||||
|
const trackUsage = jest.fn();
|
||||||
|
jest.mock('../../../hooks/useTracking', () => () => ({
|
||||||
|
trackUsage,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const makeApp = (history, trackedEvent) => (
|
||||||
|
<Router history={history}>
|
||||||
|
<ThemeProvider theme={lightTheme}>
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<SearchURLQuery label="Search label" trackedEvent={trackedEvent} />
|
||||||
|
</IntlProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('<SearchURLQuery />', () => {
|
||||||
|
it('renders and matches the snapshot', () => {
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
const { container } = render(makeApp(history));
|
||||||
|
|
||||||
|
expect(container).toMatchInlineSnapshot(`
|
||||||
|
.c2 {
|
||||||
|
border: 0;
|
||||||
|
-webkit-clip: rect(0 0 0 0);
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0 {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #dcdce4;
|
||||||
|
position: relative;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0 svg {
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0 svg > g,
|
||||||
|
.c0 svg path {
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0[aria-disabled='true'] {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:after {
|
||||||
|
-webkit-transition-property: all;
|
||||||
|
transition-property: all;
|
||||||
|
-webkit-transition-duration: 0.2s;
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
border-radius: 8px;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
bottom: -4px;
|
||||||
|
left: -4px;
|
||||||
|
right: -4px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:focus-visible:after {
|
||||||
|
border-radius: 8px;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
bottom: -5px;
|
||||||
|
left: -5px;
|
||||||
|
right: -5px;
|
||||||
|
border: 2px solid #4945ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1 {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
-webkit-box-pack: center;
|
||||||
|
-webkit-justify-content: center;
|
||||||
|
-ms-flex-pack: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1 svg > g,
|
||||||
|
.c1 svg path {
|
||||||
|
fill: #8e8ea9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1:hover svg > g,
|
||||||
|
.c1:hover svg path {
|
||||||
|
fill: #666687;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1:active svg > g,
|
||||||
|
.c1:active svg path {
|
||||||
|
fill: #a5a5ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1[aria-disabled='true'] {
|
||||||
|
background-color: #eaeaef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1[aria-disabled='true'] svg path {
|
||||||
|
fill: #666687;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
<button
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-labelledby="tooltip-1"
|
||||||
|
class="c0 c1"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M23.813 20.163l-5.3-5.367a9.792 9.792 0 001.312-4.867C19.825 4.455 15.375 0 9.913 0 4.45 0 0 4.455 0 9.929c0 5.473 4.45 9.928 9.912 9.928a9.757 9.757 0 005.007-1.4l5.275 5.35a.634.634 0 00.913 0l2.706-2.737a.641.641 0 000-.907zM9.91 3.867c3.338 0 6.05 2.718 6.05 6.061s-2.712 6.061-6.05 6.061c-3.337 0-6.05-2.718-6.05-6.06 0-3.344 2.713-6.062 6.05-6.062z"
|
||||||
|
fill="#32324D"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="c2"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
aria-live="polite"
|
||||||
|
aria-relevant="all"
|
||||||
|
id="live-region-log"
|
||||||
|
role="log"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
aria-live="polite"
|
||||||
|
aria-relevant="all"
|
||||||
|
id="live-region-status"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
aria-live="assertive"
|
||||||
|
aria-relevant="all"
|
||||||
|
id="live-region-alert"
|
||||||
|
role="alert"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toggle searchbar form and searchbar', async () => {
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
const { container } = render(makeApp(history));
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('button[type="button"]'));
|
||||||
|
|
||||||
|
expect(container).toMatchInlineSnapshot(`
|
||||||
|
.c11 {
|
||||||
|
border: 0;
|
||||||
|
-webkit-clip: rect(0 0 0 0);
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2 {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.33;
|
||||||
|
color: #32324d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c6 {
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c4 {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: row;
|
||||||
|
-ms-flex-direction: row;
|
||||||
|
flex-direction: row;
|
||||||
|
-webkit-box-pack: justify;
|
||||||
|
-webkit-justify-content: space-between;
|
||||||
|
-ms-flex-pack: justify;
|
||||||
|
justify-content: space-between;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c8 {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: row;
|
||||||
|
-ms-flex-direction: row;
|
||||||
|
flex-direction: row;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c10 {
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 16px;
|
||||||
|
color: #32324d;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c10::-webkit-input-placeholder {
|
||||||
|
color: #8e8ea9;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c10::-moz-placeholder {
|
||||||
|
color: #8e8ea9;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c10:-ms-input-placeholder {
|
||||||
|
color: #8e8ea9;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c10::placeholder {
|
||||||
|
color: #8e8ea9;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c10[aria-disabled='true'] {
|
||||||
|
background: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c10:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c5 {
|
||||||
|
border: 1px solid #dcdce4;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #ffffff;
|
||||||
|
height: 2rem;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0;
|
||||||
|
-webkit-transition-property: border-color,box-shadow,fill;
|
||||||
|
transition-property: border-color,box-shadow,fill;
|
||||||
|
-webkit-transition-duration: 0.2s;
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c5:focus-within {
|
||||||
|
border: 1px solid #4945ff;
|
||||||
|
box-shadow: #4945ff 0px 0px 0px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1 {
|
||||||
|
border: 0;
|
||||||
|
-webkit-clip: rect(0 0 0 0);
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c9 {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c9 svg path {
|
||||||
|
fill: #32324d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0 {
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0;
|
||||||
|
-webkit-transition-property: border-color,box-shadow,fill;
|
||||||
|
transition-property: border-color,box-shadow,fill;
|
||||||
|
-webkit-transition-duration: 0.2s;
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:focus-within .c7 svg path {
|
||||||
|
fill: #4945ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0 .c3 {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0 .c3:focus-within {
|
||||||
|
border: 1px solid #4945ff;
|
||||||
|
box-shadow: #4945ff 0px 0px 0px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<form
|
||||||
|
role="search"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c0"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="c1"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="c2"
|
||||||
|
for="field-1"
|
||||||
|
>
|
||||||
|
Search label
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c3 c4 c5"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c6"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c7 c8 c9"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="none"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M23.813 20.163l-5.3-5.367a9.792 9.792 0 001.312-4.867C19.825 4.455 15.375 0 9.913 0 4.45 0 0 4.455 0 9.929c0 5.473 4.45 9.928 9.912 9.928a9.757 9.757 0 005.007-1.4l5.275 5.35a.634.634 0 00.913 0l2.706-2.737a.641.641 0 000-.907zM9.91 3.867c3.338 0 6.05 2.718 6.05 6.061s-2.712 6.061-6.05 6.061c-3.337 0-6.05-2.718-6.05-6.06 0-3.344 2.713-6.062 6.05-6.062z"
|
||||||
|
fill="#32324D"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-invalid="false"
|
||||||
|
class="c10"
|
||||||
|
id="field-1"
|
||||||
|
name="search"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c11"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
aria-live="polite"
|
||||||
|
aria-relevant="all"
|
||||||
|
id="live-region-log"
|
||||||
|
role="log"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
aria-live="polite"
|
||||||
|
aria-relevant="all"
|
||||||
|
id="live-region-status"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
aria-live="assertive"
|
||||||
|
aria-relevant="all"
|
||||||
|
id="live-region-alert"
|
||||||
|
role="alert"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should push value to query params', async () => {
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
const { container } = render(makeApp(history));
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('button[type="button"]'));
|
||||||
|
|
||||||
|
const input = container.querySelector('input[name="search"]');
|
||||||
|
fireEvent.change(input, { target: { value: 'michka' } });
|
||||||
|
fireEvent.submit(input);
|
||||||
|
|
||||||
|
const urlSearchQuery = history.location.search;
|
||||||
|
expect(urlSearchQuery).toEqual('?_q=michka&page=1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear value and update query params', async () => {
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
const { container } = render(makeApp(history));
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('button[type="button"]'));
|
||||||
|
|
||||||
|
const input = container.querySelector('input[name="search"]');
|
||||||
|
fireEvent.change(input, { target: { value: 'michka' } });
|
||||||
|
fireEvent.submit(input);
|
||||||
|
|
||||||
|
const urlSearchQuery = history.location.search;
|
||||||
|
expect(urlSearchQuery).toEqual('?_q=michka&page=1');
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('button[aria-label="Clear"]'));
|
||||||
|
|
||||||
|
expect(input.value).toEqual('');
|
||||||
|
|
||||||
|
const clearedUrlSearchQuery = history.location.search;
|
||||||
|
expect(clearedUrlSearchQuery).toEqual('?page=1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.only('should call trackUsage with trackedEvent props when submit', async () => {
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
const { container } = render(makeApp(history, 'thisEvent'));
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('button[type="button"]'));
|
||||||
|
|
||||||
|
const input = container.querySelector('input[name="search"]');
|
||||||
|
fireEvent.change(input, { target: { value: 'michka' } });
|
||||||
|
fireEvent.submit(input);
|
||||||
|
|
||||||
|
expect(trackUsage.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -182,7 +182,7 @@ export * from './components/InjectionZone';
|
|||||||
export { default as LoadingIndicatorPage } from './components/LoadingIndicatorPage';
|
export { default as LoadingIndicatorPage } from './components/LoadingIndicatorPage';
|
||||||
export { default as NotAllowedInput } from './components/NotAllowedInput';
|
export { default as NotAllowedInput } from './components/NotAllowedInput';
|
||||||
export { default as SettingsPageTitle } from './components/SettingsPageTitle';
|
export { default as SettingsPageTitle } from './components/SettingsPageTitle';
|
||||||
export { default as Search } from './components/Search';
|
export { default as SearchURLQuery } from './components/SearchURLQuery';
|
||||||
export { default as Status } from './components/Status';
|
export { default as Status } from './components/Status';
|
||||||
export { default as FilterListURLQuery } from './components/FilterListURLQuery';
|
export { default as FilterListURLQuery } from './components/FilterListURLQuery';
|
||||||
export { default as FilterPopoverURLQuery } from './components/FilterPopoverURLQuery';
|
export { default as FilterPopoverURLQuery } from './components/FilterPopoverURLQuery';
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
NoPermissions,
|
NoPermissions,
|
||||||
NoMedia,
|
NoMedia,
|
||||||
AnErrorOccurred,
|
AnErrorOccurred,
|
||||||
Search,
|
SearchURLQuery,
|
||||||
useSelectionState,
|
useSelectionState,
|
||||||
useQueryParams,
|
useQueryParams,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
@ -124,7 +124,7 @@ export const MediaLibrary = () => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
endActions={
|
endActions={
|
||||||
<Search
|
<SearchURLQuery
|
||||||
label={formatMessage({
|
label={formatMessage({
|
||||||
id: getTrad('search.label'),
|
id: getTrad('search.label'),
|
||||||
defaultMessage: 'Search for an asset',
|
defaultMessage: 'Search for an asset',
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import {
|
|||||||
useRBAC,
|
useRBAC,
|
||||||
NoPermissions,
|
NoPermissions,
|
||||||
LoadingIndicatorPage,
|
LoadingIndicatorPage,
|
||||||
Search,
|
SearchURLQuery,
|
||||||
useQueryParams,
|
useQueryParams,
|
||||||
EmptyStateLayout,
|
EmptyStateLayout,
|
||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
@ -137,7 +137,7 @@ const RoleListPage = () => {
|
|||||||
})}
|
})}
|
||||||
primaryAction={
|
primaryAction={
|
||||||
<CheckPermissions permissions={permissions.createRole}>
|
<CheckPermissions permissions={permissions.createRole}>
|
||||||
<Button onClick={handleNewRoleClick} startIcon={<Plus />}>
|
<Button onClick={handleNewRoleClick} startIcon={<Plus />} size="L">
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: getTrad('List.button.roles'),
|
id: getTrad('List.button.roles'),
|
||||||
defaultMessage: 'Add new role',
|
defaultMessage: 'Add new role',
|
||||||
@ -149,7 +149,7 @@ const RoleListPage = () => {
|
|||||||
|
|
||||||
<ActionLayout
|
<ActionLayout
|
||||||
startActions={
|
startActions={
|
||||||
<Search
|
<SearchURLQuery
|
||||||
label={formatMessage({
|
label={formatMessage({
|
||||||
id: 'app.component.search.label',
|
id: 'app.component.search.label',
|
||||||
defaultMessage: 'Search',
|
defaultMessage: 'Search',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user