fix(ui): consistent searchbar across folders and lists (#13712)
Search bar styling was inconsistent across folder and list views. This re-uses the SearchBar component in the ListHeader component.
This commit is contained in:
@@ -6,101 +6,21 @@
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
&__wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: base(0.5);
|
||||
background-color: var(--theme-elevation-50);
|
||||
border-radius: var(--style-radius-m);
|
||||
}
|
||||
|
||||
&__search {
|
||||
display: flex;
|
||||
background-color: var(--theme-elevation-50);
|
||||
border-radius: var(--style-radius-m);
|
||||
gap: base(0.4);
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.icon--search {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translate3d(0, -50%, 0);
|
||||
inset-inline-start: base(0.4);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.search-filter {
|
||||
flex-grow: 1;
|
||||
|
||||
input {
|
||||
height: 46px;
|
||||
padding-left: 36px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__custom-control {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--base) / 4);
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.pill-selector,
|
||||
.where-builder,
|
||||
.sort-complex,
|
||||
.group-by-builder {
|
||||
margin-top: base(1);
|
||||
margin-top: calc(var(--base) / 2);
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
&__wrap {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
background-color: transparent;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.search-filter {
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
height: 40px;
|
||||
padding: 0 base(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
padding-right: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
||||
.search-bar__actions {
|
||||
.pill {
|
||||
padding: base(0.2) base(0.2) base(0.2) base(0.4);
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.pill-selector,
|
||||
.where-builder,
|
||||
.sort-complex {
|
||||
margin-top: calc(var(--base) / 2);
|
||||
}
|
||||
|
||||
&__toggle-columns,
|
||||
&__toggle-where,
|
||||
&__toggle-sort,
|
||||
|
||||
@@ -11,7 +11,6 @@ import { Popup } from '../../elements/Popup/index.js'
|
||||
import { useUseTitleField } from '../../hooks/useUseAsTitle.js'
|
||||
import { ChevronIcon } from '../../icons/Chevron/index.js'
|
||||
import { Dots } from '../../icons/Dots/index.js'
|
||||
import { SearchIcon } from '../../icons/Search/index.js'
|
||||
import { useListQuery } from '../../providers/ListQuery/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { AnimateHeight } from '../AnimateHeight/index.js'
|
||||
@@ -19,7 +18,7 @@ import { ColumnSelector } from '../ColumnSelector/index.js'
|
||||
import { GroupByBuilder } from '../GroupByBuilder/index.js'
|
||||
import { Pill } from '../Pill/index.js'
|
||||
import { QueryPresetBar } from '../QueryPresets/QueryPresetBar/index.js'
|
||||
import { SearchFilter } from '../SearchFilter/index.js'
|
||||
import { SearchBar } from '../SearchBar/index.js'
|
||||
import { WhereBuilder } from '../WhereBuilder/index.js'
|
||||
import { getTextFieldsToBeSearched } from './getTextFieldsToBeSearched.js'
|
||||
import './index.scss'
|
||||
@@ -132,67 +131,64 @@ export const ListControls: React.FC<ListControlsProps> = (props) => {
|
||||
queryPresetPermissions={queryPresetPermissions}
|
||||
/>
|
||||
)}
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<div className={`${baseClass}__search`}>
|
||||
<SearchIcon />
|
||||
<SearchFilter
|
||||
handleChange={handleSearchChange}
|
||||
key={collectionSlug}
|
||||
label={searchLabelTranslated.current}
|
||||
searchQueryParam={query?.search}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__buttons`}>
|
||||
{!smallBreak && <React.Fragment>{beforeActions && beforeActions}</React.Fragment>}
|
||||
{enableColumns && (
|
||||
<SearchBar
|
||||
Actions={[
|
||||
!smallBreak && (
|
||||
<React.Fragment key="before-actions">{beforeActions && beforeActions}</React.Fragment>
|
||||
),
|
||||
enableColumns && (
|
||||
<Pill
|
||||
aria-controls={`${baseClass}-columns`}
|
||||
aria-expanded={visibleDrawer === 'columns'}
|
||||
className={`${baseClass}__toggle-columns`}
|
||||
icon={<ChevronIcon direction={visibleDrawer === 'columns' ? 'up' : 'down'} />}
|
||||
id="toggle-list-columns"
|
||||
key="toggle-list-columns"
|
||||
onClick={() => setVisibleDrawer(visibleDrawer !== 'columns' ? 'columns' : undefined)}
|
||||
pillStyle="light"
|
||||
size="small"
|
||||
>
|
||||
{t('general:columns')}
|
||||
</Pill>
|
||||
)}
|
||||
{enableFilters && (
|
||||
),
|
||||
enableFilters && (
|
||||
<Pill
|
||||
aria-controls={`${baseClass}-where`}
|
||||
aria-expanded={visibleDrawer === 'where'}
|
||||
className={`${baseClass}__toggle-where`}
|
||||
icon={<ChevronIcon direction={visibleDrawer === 'where' ? 'up' : 'down'} />}
|
||||
id="toggle-list-filters"
|
||||
key="toggle-list-filters"
|
||||
onClick={() => setVisibleDrawer(visibleDrawer !== 'where' ? 'where' : undefined)}
|
||||
pillStyle="light"
|
||||
size="small"
|
||||
>
|
||||
{t('general:filters')}
|
||||
</Pill>
|
||||
)}
|
||||
{enableSort && (
|
||||
),
|
||||
enableSort && (
|
||||
<Pill
|
||||
aria-controls={`${baseClass}-sort`}
|
||||
aria-expanded={visibleDrawer === 'sort'}
|
||||
className={`${baseClass}__toggle-sort`}
|
||||
icon={<ChevronIcon />}
|
||||
id="toggle-list-sort"
|
||||
key="toggle-list-sort"
|
||||
onClick={() => setVisibleDrawer(visibleDrawer !== 'sort' ? 'sort' : undefined)}
|
||||
pillStyle="light"
|
||||
size="small"
|
||||
>
|
||||
{t('general:sort')}
|
||||
</Pill>
|
||||
)}
|
||||
{collectionConfig.admin.groupBy && (
|
||||
),
|
||||
collectionConfig.admin.groupBy && (
|
||||
<Pill
|
||||
aria-controls={`${baseClass}-group-by`}
|
||||
aria-expanded={visibleDrawer === 'group-by'}
|
||||
className={`${baseClass}__toggle-group-by`}
|
||||
icon={<ChevronIcon direction={visibleDrawer === 'group-by' ? 'up' : 'down'} />}
|
||||
id="toggle-group-by"
|
||||
key="toggle-group-by"
|
||||
onClick={() =>
|
||||
setVisibleDrawer(visibleDrawer !== 'group-by' ? 'group-by' : undefined)
|
||||
}
|
||||
@@ -203,13 +199,14 @@ export const ListControls: React.FC<ListControlsProps> = (props) => {
|
||||
label: '',
|
||||
})}
|
||||
</Pill>
|
||||
)}
|
||||
{listMenuItems && Array.isArray(listMenuItems) && listMenuItems.length > 0 && (
|
||||
),
|
||||
listMenuItems && Array.isArray(listMenuItems) && listMenuItems.length > 0 && (
|
||||
<Popup
|
||||
button={<Dots ariaLabel={t('general:moreOptions')} />}
|
||||
className={`${baseClass}__popup`}
|
||||
horizontalAlign="right"
|
||||
id="list-menu"
|
||||
key="list-menu"
|
||||
size="small"
|
||||
verticalAlign="bottom"
|
||||
>
|
||||
@@ -217,9 +214,13 @@ export const ListControls: React.FC<ListControlsProps> = (props) => {
|
||||
<Fragment key={`list-menu-item-${i}`}>{item}</Fragment>
|
||||
))}
|
||||
</Popup>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
key={collectionSlug}
|
||||
label={searchLabelTranslated.current}
|
||||
onSearchChange={handleSearchChange}
|
||||
searchQueryParam={query?.search}
|
||||
/>
|
||||
{enableColumns && (
|
||||
<AnimateHeight
|
||||
className={`${baseClass}__columns`}
|
||||
|
||||
@@ -2,18 +2,41 @@
|
||||
|
||||
@layer payload-default {
|
||||
.search-bar {
|
||||
--icon-width: 40px;
|
||||
--search-bg: var(--theme-elevation-50);
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--theme-elevation-50);
|
||||
background-color: var(--search-bg);
|
||||
border-radius: var(--style-radius-m);
|
||||
padding: calc(var(--base) * 0.6);
|
||||
gap: calc(var(--base) * 0.6);
|
||||
position: relative;
|
||||
min-height: 46px;
|
||||
isolation: isolate;
|
||||
|
||||
&:has(.search-bar__actions) {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
}
|
||||
|
||||
.icon--search {
|
||||
grid-column: 1/2;
|
||||
grid-row: 1/2;
|
||||
z-index: 1;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
pointer-events: none;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.search-filter {
|
||||
flex-grow: 1;
|
||||
grid-column: 1/3;
|
||||
grid-row: 1/2;
|
||||
background-color: transparent;
|
||||
border-radius: inherit;
|
||||
input {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
padding: calc(var(--base) * 0.5) var(--base) calc(var(--base) * 0.5) var(--icon-width);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +44,32 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--base) / 4);
|
||||
padding: calc(var(--base) * 0.5);
|
||||
grid-column: 3/4;
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
min-height: 40px;
|
||||
background-color: transparent;
|
||||
|
||||
&:has(.search-bar__actions) {
|
||||
row-gap: calc(var(--base) / 2);
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
|
||||
.search-filter {
|
||||
background-color: var(--search-bg);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
grid-row: 2/3;
|
||||
grid-column: 1/3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--base) / 4);
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,14 @@ const baseClass = 'search-bar'
|
||||
type SearchBarProps = {
|
||||
Actions?: React.ReactNode[]
|
||||
className?: string
|
||||
filterKey?: string
|
||||
label?: string
|
||||
onSearchChange?: (search: string) => void
|
||||
onSearchChange: (search: string) => void
|
||||
searchQueryParam?: string
|
||||
}
|
||||
export function SearchBar({
|
||||
Actions,
|
||||
className,
|
||||
filterKey,
|
||||
label,
|
||||
label = 'Search...',
|
||||
onSearchChange,
|
||||
searchQueryParam,
|
||||
}: SearchBarProps) {
|
||||
@@ -25,7 +23,6 @@ export function SearchBar({
|
||||
<SearchIcon />
|
||||
<SearchFilter
|
||||
handleChange={onSearchChange}
|
||||
key={filterKey || 'search'}
|
||||
label={label}
|
||||
searchQueryParam={searchQueryParam}
|
||||
/>
|
||||
|
||||
@@ -1,51 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
|
||||
export type SearchFilterProps = {
|
||||
/**
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
fieldName?: string
|
||||
handleChange?: (search: string) => void
|
||||
/**
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
*
|
||||
* Prefer passing in `searchString` instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
initialParams?: ParsedQs
|
||||
label: string
|
||||
searchQueryParam?: string
|
||||
/**
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
setValue?: (arg: string) => void
|
||||
/**
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
value?: string
|
||||
}
|
||||
|
||||
import type { ParsedQs } from 'qs-esm'
|
||||
|
||||
import { usePathname } from 'next/navigation.js'
|
||||
import type { SearchFilterProps } from './types.js'
|
||||
|
||||
import { useDebounce } from '../../hooks/useDebounce.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'search-filter'
|
||||
|
||||
export const SearchFilter: React.FC<SearchFilterProps> = (props) => {
|
||||
export function SearchFilter(props: SearchFilterProps) {
|
||||
const { handleChange, initialParams, label, searchQueryParam } = props
|
||||
const searchParam = initialParams?.search || searchQueryParam
|
||||
const pathname = usePathname()
|
||||
const [search, setSearch] = useState(typeof searchParam === 'string' ? searchParam : undefined)
|
||||
|
||||
/**
|
||||
@@ -67,7 +33,12 @@ export const SearchFilter: React.FC<SearchFilterProps> = (props) => {
|
||||
setSearch(searchParam as string)
|
||||
previousSearch.current = searchParam as string
|
||||
}
|
||||
}, [searchParam, pathname])
|
||||
|
||||
return () => {
|
||||
shouldUpdateState.current = true
|
||||
previousSearch.current = undefined
|
||||
}
|
||||
}, [searchParam])
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedSearch !== previousSearch.current && shouldUpdateState.current) {
|
||||
|
||||
33
packages/ui/src/elements/SearchFilter/types.ts
Normal file
33
packages/ui/src/elements/SearchFilter/types.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ParsedQs } from 'qs-esm'
|
||||
|
||||
export type SearchFilterProps = {
|
||||
/**
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
fieldName?: string
|
||||
handleChange?: (search: string) => void
|
||||
/**
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
*
|
||||
* Prefer passing in `searchString` instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
initialParams?: ParsedQs
|
||||
label: string
|
||||
searchQueryParam?: string
|
||||
/**
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
setValue?: (arg: string) => void
|
||||
/**
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
value?: string
|
||||
}
|
||||
Reference in New Issue
Block a user