From 09e3174834e779f879cb4ee318a473645af894b6 Mon Sep 17 00:00:00 2001 From: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com> Date: Fri, 5 Sep 2025 16:40:11 -0400 Subject: [PATCH] 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. --- .../ui/src/elements/ListControls/index.scss | 84 +------------------ .../ui/src/elements/ListControls/index.tsx | 53 ++++++------ packages/ui/src/elements/SearchBar/index.scss | 63 ++++++++++++-- packages/ui/src/elements/SearchBar/index.tsx | 7 +- .../ui/src/elements/SearchFilter/index.tsx | 47 ++--------- .../ui/src/elements/SearchFilter/types.ts | 33 ++++++++ 6 files changed, 129 insertions(+), 158 deletions(-) create mode 100644 packages/ui/src/elements/SearchFilter/types.ts diff --git a/packages/ui/src/elements/ListControls/index.scss b/packages/ui/src/elements/ListControls/index.scss index 037080a966..0679c89dfc 100644 --- a/packages/ui/src/elements/ListControls/index.scss +++ b/packages/ui/src/elements/ListControls/index.scss @@ -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, diff --git a/packages/ui/src/elements/ListControls/index.tsx b/packages/ui/src/elements/ListControls/index.tsx index 37e5abe066..9930dec743 100644 --- a/packages/ui/src/elements/ListControls/index.tsx +++ b/packages/ui/src/elements/ListControls/index.tsx @@ -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 = (props) => { queryPresetPermissions={queryPresetPermissions} /> )} -
-
- - -
-
- {!smallBreak && {beforeActions && beforeActions}} - {enableColumns && ( + {beforeActions && beforeActions} + ), + enableColumns && ( } id="toggle-list-columns" + key="toggle-list-columns" onClick={() => setVisibleDrawer(visibleDrawer !== 'columns' ? 'columns' : undefined)} pillStyle="light" size="small" > {t('general:columns')} - )} - {enableFilters && ( + ), + enableFilters && ( } id="toggle-list-filters" + key="toggle-list-filters" onClick={() => setVisibleDrawer(visibleDrawer !== 'where' ? 'where' : undefined)} pillStyle="light" size="small" > {t('general:filters')} - )} - {enableSort && ( + ), + enableSort && ( } id="toggle-list-sort" + key="toggle-list-sort" onClick={() => setVisibleDrawer(visibleDrawer !== 'sort' ? 'sort' : undefined)} pillStyle="light" size="small" > {t('general:sort')} - )} - {collectionConfig.admin.groupBy && ( + ), + collectionConfig.admin.groupBy && ( } 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 = (props) => { label: '', })} - )} - {listMenuItems && Array.isArray(listMenuItems) && listMenuItems.length > 0 && ( + ), + listMenuItems && Array.isArray(listMenuItems) && listMenuItems.length > 0 && ( } className={`${baseClass}__popup`} horizontalAlign="right" id="list-menu" + key="list-menu" size="small" verticalAlign="bottom" > @@ -217,9 +214,13 @@ export const ListControls: React.FC = (props) => { {item} ))} - )} -
-
+ ), + ].filter(Boolean)} + key={collectionSlug} + label={searchLabelTranslated.current} + onSearchChange={handleSearchChange} + searchQueryParam={query?.search} + /> {enableColumns && ( 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({ diff --git a/packages/ui/src/elements/SearchFilter/index.tsx b/packages/ui/src/elements/SearchFilter/index.tsx index 4f09fc9f18..c0407824d9 100644 --- a/packages/ui/src/elements/SearchFilter/index.tsx +++ b/packages/ui/src/elements/SearchFilter/index.tsx @@ -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 = (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 = (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) { diff --git a/packages/ui/src/elements/SearchFilter/types.ts b/packages/ui/src/elements/SearchFilter/types.ts new file mode 100644 index 0000000000..2b52da7937 --- /dev/null +++ b/packages/ui/src/elements/SearchFilter/types.ts @@ -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 +}