From 6137785fc04f8871ad79ec192e02e2fd7059a0ef Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Mon, 8 Dec 2025 13:05:25 +1000 Subject: [PATCH] filter styles and variant --- .../grid/components/header/Header.tsx | 6 +-- .../header/filter/FilterPopover.tsx | 6 ++- .../Functions/FunctionsList/FunctionsList.tsx | 4 +- .../ui-patterns/src/FilterBar/FilterBar.tsx | 20 ++++++--- .../src/FilterBar/FilterBarContext.tsx | 37 +++++++++------- .../src/FilterBar/FilterCondition.tsx | 29 +++++++------ .../ui-patterns/src/FilterBar/FilterGroup.tsx | 42 +++++++++++-------- 7 files changed, 90 insertions(+), 54 deletions(-) diff --git a/apps/studio/components/grid/components/header/Header.tsx b/apps/studio/components/grid/components/header/Header.tsx index b45336fb2d6..e666d948de6 100644 --- a/apps/studio/components/grid/components/header/Header.tsx +++ b/apps/studio/components/grid/components/header/Header.tsx @@ -64,8 +64,8 @@ export type HeaderProps = { const DefaultHeader = () => { return ( -
-
+
+
@@ -202,7 +202,7 @@ export const Header = ({ customHeader, isRefetching, tableQueriesEnabled = true return (
-
+
{customHeader ? ( customHeader ) : snap.selectedRows.size > 0 ? ( diff --git a/apps/studio/components/grid/components/header/filter/FilterPopover.tsx b/apps/studio/components/grid/components/header/filter/FilterPopover.tsx index 237184a26af..85c22f0f0ea 100644 --- a/apps/studio/components/grid/components/header/filter/FilterPopover.tsx +++ b/apps/studio/components/grid/components/header/filter/FilterPopover.tsx @@ -139,7 +139,10 @@ export const FilterPopover = ({ portal = true }: FilterPopoverProps) => { // Create filter properties from table columns const filterProperties: FilterProperty[] = useMemo(() => { const columns = snap.table?.columns ?? [] - const defaultOperators = FilterOperatorOptions.map((op) => ({ value: op.value, label: op.label })) + const defaultOperators = FilterOperatorOptions.map((op) => ({ + value: op.value, + label: op.label, + })) const stringOperators = [ { value: '~~*', label: 'ilike operator' }, ...defaultOperators.filter((op) => op.value !== '~~*'), @@ -267,6 +270,7 @@ export const FilterPopover = ({ portal = true }: FilterPopoverProps) => { onFreeformTextChange={setFreeformText} actions={actions} isLoading={isGenerating} + variant="pill" className="bg-transparent border-0" /> ) diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx index 6591c4b78e0..abac7e52019 100644 --- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx +++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx @@ -274,7 +274,7 @@ const FunctionsList = () => { disabled={!canCreateFunctions} onClick={() => createFunction()} className="flex-grow" - size="small" + size="tiny" tooltip={{ content: { side: 'bottom', @@ -288,7 +288,7 @@ const FunctionsList = () => { } diff --git a/packages/ui-patterns/src/FilterBar/FilterBar.tsx b/packages/ui-patterns/src/FilterBar/FilterBar.tsx index fcad80df731..63b8cbb072c 100644 --- a/packages/ui-patterns/src/FilterBar/FilterBar.tsx +++ b/packages/ui-patterns/src/FilterBar/FilterBar.tsx @@ -3,7 +3,7 @@ import { motion } from 'framer-motion' import { Search } from 'lucide-react' import { cn } from 'ui' -import { FilterBarRoot, useFilterBar } from './FilterBarContext' +import { FilterBarRoot, useFilterBar, type FilterBarVariant } from './FilterBarContext' import { FilterGroup } from './FilterGroup' import { FilterBarAction, FilterGroup as FilterGroupType, FilterProperty } from './types' @@ -17,22 +17,30 @@ export type FilterBarProps = { isLoading?: boolean className?: string supportsOperators?: boolean + variant?: FilterBarVariant } function FilterBarContent({ className }: { className?: string }) { - const { filters, error, optionsError, isLoading } = useFilterBar() + const { filters, error, optionsError, isLoading, variant } = useFilterBar() return (
- +
+ +
diff --git a/packages/ui-patterns/src/FilterBar/FilterBarContext.tsx b/packages/ui-patterns/src/FilterBar/FilterBarContext.tsx index 1595495accf..ee3b8a7629f 100644 --- a/packages/ui-patterns/src/FilterBar/FilterBarContext.tsx +++ b/packages/ui-patterns/src/FilterBar/FilterBarContext.tsx @@ -1,18 +1,18 @@ 'use client' -import React, { createContext, useContext, useRef, useCallback } from 'react' -import { FilterBarAction, FilterProperty, FilterGroup } from './types' +import React, { createContext, useCallback, useContext, useRef } from 'react' import { ActiveInput, useFilterBarState, useOptionsCache } from './hooks' -import { useKeyboardNavigation } from './useKeyboardNavigation' -import { useCommandHandling } from './useCommandHandling' import { MenuItem } from './menuItems' +import { FilterBarAction, FilterGroup, FilterProperty } from './types' +import { useCommandHandling } from './useCommandHandling' +import { useKeyboardNavigation } from './useKeyboardNavigation' import { findConditionByPath, isAsyncOptionsFunction, removeFromGroup, - updateNestedValue, - updateNestedOperator, updateNestedLogicalOperator, + updateNestedOperator, + updateNestedValue, } from './utils' export type FilterBarContextValue = { @@ -49,6 +49,7 @@ export type FilterBarContextValue = { // Config supportsOperators: boolean + variant: FilterBarVariant actions?: FilterBarAction[] // Refs @@ -75,8 +76,11 @@ export type FilterBarRootProps = { actions?: FilterBarAction[] isLoading?: boolean supportsOperators?: boolean + variant?: FilterBarVariant } +export type FilterBarVariant = 'default' | 'pill' + export function FilterBarRoot({ children, filterProperties, @@ -87,6 +91,7 @@ export function FilterBarRoot({ actions, isLoading: externalLoading, supportsOperators = false, + variant = 'default', }: FilterBarRootProps) { const rootRef = useRef(null) @@ -100,12 +105,8 @@ export function FilterBarRoot({ setIsCommandMenuVisible, } = useFilterBarState() - const { - loadingOptions, - propertyOptionsCache, - loadPropertyOptions, - optionsError, - } = useOptionsCache() + const { loadingOptions, propertyOptionsCache, loadPropertyOptions, optionsError } = + useOptionsCache() const handleInputChange = useCallback( (path: number[], value: string) => { @@ -178,7 +179,14 @@ export function FilterBarRoot({ } } }, - [filters, filterProperties, loadPropertyOptions, setActiveInput, setIsCommandMenuVisible, hideTimeoutRef] + [ + filters, + filterProperties, + loadPropertyOptions, + setActiveInput, + setIsCommandMenuVisible, + hideTimeoutRef, + ] ) const handleOperatorFocus = useCallback( @@ -284,6 +292,7 @@ export function FilterBarRoot({ // Config supportsOperators, + variant, actions, // Refs @@ -292,7 +301,7 @@ export function FilterBarRoot({ return ( -
+
{children}
diff --git a/packages/ui-patterns/src/FilterBar/FilterCondition.tsx b/packages/ui-patterns/src/FilterBar/FilterCondition.tsx index 05dfc1c9ce3..0f6706c36d5 100644 --- a/packages/ui-patterns/src/FilterBar/FilterCondition.tsx +++ b/packages/ui-patterns/src/FilterBar/FilterCondition.tsx @@ -4,6 +4,7 @@ import { X } from 'lucide-react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Button, + cn, Input_Shadcn_, Popover_Shadcn_, PopoverAnchor_Shadcn_, @@ -44,6 +45,7 @@ export function FilterCondition({ handleRemoveCondition, handleSelectMenuItem, setActiveInput, + variant, } = useFilterBar() const operatorRef = useRef(null) @@ -190,17 +192,20 @@ export function FilterCondition({ return (
handleLabelClick(path)} > {property.label} 0}> -
+
handleOperatorFocus(path)} onBlur={handleOperatorBlur} onKeyDown={handleOperatorKeyDown} - className="border-none bg-transparent p-0 text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 h-6 text-foreground-light w-full absolute left-0 top-0" + className="h-full border-none bg-transparent py-0 px-1 text-center text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 text-foreground-light w-full absolute left-0 top-0" disabled={isLoading} aria-label={`Operator for ${property.label}`} /> - + {condition.operator || ' '}
@@ -242,7 +247,7 @@ export function FilterCondition({ 0)}> -
+
handleInputFocus(path)} onBlur={handleValueBlur} onKeyDown={handleValueKeyDown} - className="border-none bg-transparent p-0 text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 h-6 w-full absolute left-0 top-0" + className="h-full border-none bg-transparent py-0 px-1 text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 w-full absolute left-0 top-0" disabled={isLoading} aria-label={`Value for ${property.label}`} /> - + {(condition.value ?? '').toString() || ' '}
@@ -306,13 +311,13 @@ export function FilterCondition({ size="tiny" icon={ } onClick={onRemove} - className="group hover:text-foreground !hover:bg-surface-600 p-0" + className="group hover:text-foreground !hover:bg-surface-600 rounded-none px-1 h-auto py-0 px-1" aria-label={`Remove ${property.label} filter`} />
diff --git a/packages/ui-patterns/src/FilterBar/FilterGroup.tsx b/packages/ui-patterns/src/FilterBar/FilterGroup.tsx index 7aebb8f5608..660ca9ebf24 100644 --- a/packages/ui-patterns/src/FilterBar/FilterGroup.tsx +++ b/packages/ui-patterns/src/FilterBar/FilterGroup.tsx @@ -1,7 +1,13 @@ 'use client' import React, { useEffect, useMemo, useRef, useState } from 'react' -import { Input_Shadcn_, Popover_Shadcn_, PopoverAnchor_Shadcn_, PopoverContent_Shadcn_ } from 'ui' +import { + cn, + Input_Shadcn_, + Popover_Shadcn_, + PopoverAnchor_Shadcn_, + PopoverContent_Shadcn_, +} from 'ui' import { DefaultCommandList } from './DefaultCommandList' import { useFilterBar } from './FilterBarContext' import { FilterCondition } from './FilterCondition' @@ -23,6 +29,7 @@ export function FilterGroup({ group, path }: FilterGroupProps) { isLoading, supportsOperators, actions, + variant, handleInputBlur, handleGroupFreeformFocus, handleGroupFreeformChange, @@ -83,14 +90,7 @@ export function FilterGroup({ group, path }: FilterGroupProps) { actions, supportsOperators, }), - [ - filterProperties, - isActive, - freeformText, - localFreeformValue, - actions, - supportsOperators, - ] + [filterProperties, isActive, freeformText, localFreeformValue, actions, supportsOperators] ) // Only the root group should expand to fill available space @@ -115,13 +115,19 @@ export function FilterGroup({ group, path }: FilterGroupProps) { return (
0 ? "before:content-['('] before:text-foreground-muted after:content-[')'] after:text-foreground-muted" : '' - } ${isRootGroup ? 'flex-1 min-w-0' : ''}`} + } ${isRootGroup ? 'flex-1 min-w-0' : ''} ${variant === 'pill' ? 'py-2' : ''}`} > -
+
{group.conditions.map((condition, index) => { const currentPath = [...path, index] @@ -165,8 +171,10 @@ export function FilterGroup({ group, path }: FilterGroupProps) { onFocus={() => handleGroupFreeformFocus(path)} onBlur={handleFreeformBlur} onKeyDown={handleFreeformKeyDown} - className="border-none bg-transparent p-0 text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 h-6 w-full flex-1 min-w-0" - placeholder={group.conditions.length === 0 ? 'Search or filter...' : '+'} + className="border-none bg-transparent text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 w-full flex-1 h-auto min-w-0 px-2 py-0" + placeholder={ + group.conditions.length === 0 ? 'Search or filter...' : '+ Search or filter...' + } disabled={isLoading} /> ) : ( @@ -179,11 +187,11 @@ export function FilterGroup({ group, path }: FilterGroupProps) { onFocus={() => handleGroupFreeformFocus(path)} onBlur={handleFreeformBlur} onKeyDown={handleFreeformKeyDown} - className="border-none bg-transparent p-0 text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 h-6 w-full absolute left-0 top-0" - placeholder="+" + className="h-full border-none bg-transparent py-0 text-xs focus:outline-none focus:ring-0 focus:shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 w-full absolute left-0 top-0 px-2" + placeholder="+ Add filter" disabled={isLoading} /> - + {(isActive ? freeformText : localFreeformValue) || '+'}