Skip to content

Commit 994084c

Browse files
authored
Add support to have filters in input select component (#2781)
* feat: add support to have filters in Input Select component * fix: polish styles * fix: adapt calendar to compact version * fix: update weekview sizes * fix: adapt sizes for weekview * fix: add missing bottom border on select * fix: correct visual default filters picker * feat: add apply filters button * fix: make search box full width * fix: localize placeholders * refactor: remove unexpected console.log * fix: select * fix: grouping and sorting spacing around * fix: improvements * fix: some size fixes * fix: make compact prop in OneCalendar private * fix: only make select taller when having filters * fix: get rid of linter errors
1 parent 516733f commit 994084c

File tree

27 files changed

+820
-160
lines changed

27 files changed

+820
-160
lines changed

packages/react/src/components/OneFilterPicker/OneFilterPicker.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { PresetsDefinition } from "./types"
77

88
import { useEventEmitter } from "@/experimental/OneDataCollection/useEventEmitter"
99
import { cn } from "@/lib/utils"
10-
import type { FiltersDefinition, FiltersState } from "./types"
10+
import type { FiltersDefinition, FiltersMode, FiltersState } from "./types"
1111

1212
/**
1313
* Props for the Filters component.
@@ -26,6 +26,8 @@ export type OneFilterPickerRootProps<Definition extends FiltersDefinition> = {
2626
onChange: (value: FiltersState<Definition>) => void
2727
/** The children of the component */
2828
children?: React.ReactNode
29+
/** The mode of the component */
30+
mode?: FiltersMode
2931
}
3032

3133
/**
@@ -93,6 +95,7 @@ const FiltersRoot = <Definition extends FiltersDefinition>({
9395
value,
9496
children,
9597
presetsLoading = false,
98+
mode = "default",
9699
...props
97100
}: OneFilterPickerRootProps<Definition>) => {
98101
const defaultFilters = useRef(value)
@@ -126,6 +129,7 @@ const FiltersRoot = <Definition extends FiltersDefinition>({
126129
<FiltersContext.Provider
127130
value={{
128131
...props,
132+
mode,
129133
presets: props.presets as PresetsDefinition<FiltersDefinition>,
130134
presetsLoading,
131135
value: localFiltersValue,
@@ -157,6 +161,7 @@ const FiltersControls = () => {
157161
setFiltersValue,
158162
presets,
159163
emitFilterChange,
164+
mode,
160165
} = useContext(FiltersContext)
161166

162167
const shownFilters = filters
@@ -181,6 +186,7 @@ const FiltersControls = () => {
181186
onOpenChange={setIsFiltersOpen}
182187
isOpen={isFiltersOpen}
183188
hideLabel={!!presets}
189+
mode={mode}
184190
/>
185191
{!!presets?.length && (
186192
<div className="flex items-center">
@@ -277,7 +283,7 @@ const OneFilterPicker = <Definition extends FiltersDefinition>(
277283
</div>
278284
)}
279285
</div>
280-
<FiltersChipsList />
286+
{(!props.mode || props.mode === "default") && <FiltersChipsList />}
281287
</FiltersRoot>
282288
)
283289
}

packages/react/src/components/OneFilterPicker/components/FilterContent.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ type FilterContentProps<Definition extends FiltersDefinition> = {
2121
tempFilters: FiltersState<Definition>
2222
/** Callback fired when a filter value changes */
2323
onFilterChange: (key: keyof Definition, value: unknown) => void
24+
/** Tells us if we are in compact mode */
25+
isCompactMode?: boolean
2426
}
2527

2628
/**
@@ -44,6 +46,7 @@ export function FilterContent<Definition extends FiltersDefinition>({
4446
definition,
4547
tempFilters,
4648
onFilterChange,
49+
isCompactMode,
4750
}: FilterContentProps<Definition>) {
4851
if (!selectedFilterKey) return null
4952

@@ -72,12 +75,13 @@ export function FilterContent<Definition extends FiltersDefinition>({
7275
schema: T
7376
value: FilterValue<T>
7477
onChange: (v: FilterValue<T>) => void
78+
isCompactMode?: boolean
7579
}) => React.ReactNode
76-
)({ schema, value, onChange })
80+
)({ schema, value, onChange, isCompactMode })
7781
}
7882

7983
return (
80-
<div className="relative flex w-full flex-col gap-1">
84+
<div className="relative flex h-full w-full flex-col gap-1">
8185
<div className="relative flex h-full flex-col justify-between overflow-y-auto">
8286
{renderFilterForm(filter, currentValue, (value) =>
8387
onFilterChange(selectedFilterKey, value)

packages/react/src/components/OneFilterPicker/components/FilterList.tsx

Lines changed: 96 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import { Button } from "@/components/Actions/Button"
2+
import { F0Icon } from "@/components/F0Icon/F0Icon"
3+
import { F1SearchBox } from "@/experimental/Forms/Fields/F1SearchBox"
4+
import { ChevronRight } from "@/icons/app"
15
import { useI18n } from "@/lib/providers/i18n"
26
import { AnimatePresence, motion } from "motion/react"
7+
import { useState } from "react"
38
import { cn, focusRing } from "../../../lib/utils"
49
import { FilterDefinitionsByType, getFilterType } from "../filterTypes"
510
import type {
@@ -21,6 +26,10 @@ interface FilterListProps<Definition extends FiltersDefinition> {
2126
selectedFilterKey: keyof Definition | null
2227
/** Callback fired when a filter is selected from the list */
2328
onFilterSelect: (key: keyof Definition) => void
29+
/** Whether to hide a border around the list */
30+
isCompactMode?: boolean
31+
/** Callback fired when the apply filters button is clicked */
32+
onClickApplyFilters: () => void
2433
}
2534

2635
/**
@@ -41,52 +50,99 @@ export function FilterList<Definition extends FiltersDefinition>({
4150
tempFilters,
4251
selectedFilterKey,
4352
onFilterSelect,
53+
isCompactMode,
54+
onClickApplyFilters,
4455
}: FilterListProps<Definition>) {
4556
const i18n = useI18n()
57+
58+
const [searchValue, setSearchValue] = useState("")
59+
4660
return (
47-
<div className="w-[224px] shrink-0 border border-solid border-transparent border-r-f1-border-secondary">
48-
<div className="flex h-full w-full flex-col gap-1 overflow-y-auto p-2">
49-
{Object.entries(definition).map(([key, filter]) => {
50-
const filterType = getFilterType(filter.type)
61+
<div
62+
className={cn(
63+
"z-30 flex h-full w-full shrink-0 flex-col bg-f1-background",
64+
isCompactMode ? "min-w-[224px]" : "w-[224px]",
65+
!isCompactMode &&
66+
"border border-solid border-transparent border-r-f1-border-secondary"
67+
)}
68+
>
69+
<div className="flex flex-col p-2">
70+
<F1SearchBox
71+
key="filter-list-search"
72+
name="filter-list-search"
73+
placeholder={i18n.toc.search}
74+
value={searchValue}
75+
onChange={setSearchValue}
76+
clearable
77+
/>
78+
</div>
79+
<div
80+
className={cn(
81+
"flex h-full w-full flex-col gap-1 overflow-y-auto p-2 pt-0",
82+
isCompactMode && "px-1 py-0"
83+
)}
84+
>
85+
{isCompactMode && (
86+
<div className="-mx-2 mb-1 h-px border-0 border-t border-solid border-f1-border-secondary" />
87+
)}
88+
<div className="flex flex-1 flex-col gap-1">
89+
{Object.entries(definition).map(([key, filter]) => {
90+
const matchesWithSearch =
91+
!searchValue ||
92+
filter.label.toLowerCase().includes(searchValue.toLowerCase())
5193

52-
type FilterType = FilterDefinitionsByType[typeof filter.type]
53-
const currentValue = tempFilters[key] as FilterValue<FilterType>
54-
const typedFilterType = filterType as FilterTypeDefinition<
55-
FilterValue<FilterType>
56-
>
94+
if (!matchesWithSearch) return null
5795

58-
return (
59-
<button
60-
key={key}
61-
className={cn(
62-
"group relative flex w-full appearance-none items-center justify-between rounded px-2 py-1.5 font-medium transition-colors",
63-
"hover:bg-f1-background-secondary",
64-
selectedFilterKey === key && "bg-f1-background-secondary",
65-
focusRing()
66-
)}
67-
onClick={() => onFilterSelect(key as keyof Definition)}
96+
const filterType = getFilterType(filter.type)
97+
98+
type FilterType = FilterDefinitionsByType[typeof filter.type]
99+
const currentValue = tempFilters[key] as FilterValue<FilterType>
100+
const typedFilterType = filterType as FilterTypeDefinition<
101+
FilterValue<FilterType>
68102
>
69-
<div className="flex items-center justify-start gap-2.5">
70-
<span className="line-clamp-1 w-fit text-left">
71-
{filter.label}
72-
</span>
73-
<AnimatePresence>
74-
{!typedFilterType.isEmpty(currentValue, {
75-
schema: filter as unknown as FilterTypeSchema,
76-
i18n,
77-
}) && (
78-
<motion.div
79-
className="h-2 w-2 shrink-0 rounded-full bg-f1-background-selected-bold"
80-
initial={{ opacity: 0, scale: 0.7 }}
81-
animate={{ opacity: 1, scale: 1 }}
82-
exit={{ opacity: 0, scale: 0.7 }}
83-
/>
84-
)}
85-
</AnimatePresence>
86-
</div>
87-
</button>
88-
)
89-
})}
103+
104+
return (
105+
<button
106+
key={key}
107+
className={cn(
108+
"group relative flex w-full appearance-none items-center justify-between rounded px-2 py-1.5 font-medium transition-colors",
109+
"hover:bg-f1-background-secondary",
110+
selectedFilterKey === key && "bg-f1-background-secondary",
111+
focusRing()
112+
)}
113+
onClick={() => onFilterSelect(key as keyof Definition)}
114+
>
115+
<div className="flex w-full items-center justify-start gap-2.5">
116+
<span className="line-clamp-1 w-fit flex-1 text-left">
117+
{filter.label}
118+
</span>
119+
<AnimatePresence>
120+
{!typedFilterType.isEmpty(currentValue, {
121+
schema: filter as unknown as FilterTypeSchema,
122+
i18n,
123+
}) && (
124+
<motion.div
125+
className="h-2 w-2 shrink-0 rounded-full bg-f1-background-selected-bold"
126+
initial={{ opacity: 0, scale: 0.7 }}
127+
animate={{ opacity: 1, scale: 1 }}
128+
exit={{ opacity: 0, scale: 0.7 }}
129+
/>
130+
)}
131+
</AnimatePresence>
132+
{isCompactMode && <F0Icon icon={ChevronRight} />}
133+
</div>
134+
</button>
135+
)
136+
})}
137+
</div>
138+
{isCompactMode && (
139+
<div className="flex items-center justify-end gap-2 border border-solid border-transparent border-t-f1-border-secondary bg-f1-background p-2">
140+
<Button
141+
onClick={onClickApplyFilters}
142+
label={i18n.filters.applyFilters}
143+
/>
144+
</div>
145+
)}
90146
</div>
91147
</div>
92148
)

0 commit comments

Comments
 (0)