Skip to content

Commit ca04294

Browse files
chore: select allow unknown
1 parent 37a159f commit ca04294

File tree

2 files changed

+50
-34
lines changed
  • packages/react/src/experimental
    • Forms/Fields/Select
    • Navigation/Header/Breadcrumbs/internal/BreadcrumbSelect

2 files changed

+50
-34
lines changed

packages/react/src/experimental/Forms/Fields/Select/index.tsx

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,27 @@ import { SelectTopActions } from "./SelectTopActions"
4343
import type { SelectItemObject, SelectItemProps } from "./types"
4444
export * from "./types"
4545

46+
// Helper type to resolve the actual record type
47+
export type ResolvedRecordType<R> = R extends RecordType ? R : RecordType
48+
4649
/**
4750
* Select component for choosing from a list of options.
4851
*
4952
* @template T - The type of the emitted value
5053
* @template R - The type of the record/item data (used with data source)
5154
*
5255
*/
53-
export type SelectProps<T extends string, R extends RecordType = RecordType> = {
56+
export type SelectProps<T extends string, R = unknown> = {
5457
onChange: (
5558
value: T,
56-
originalItem?: R,
57-
option?: SelectItemObject<T, R>
59+
originalItem?: ResolvedRecordType<R>,
60+
option?: SelectItemObject<T, ResolvedRecordType<R>>
61+
) => void
62+
onChangeSelectedOption?: (
63+
option: SelectItemObject<T, ResolvedRecordType<R>>
5864
) => void
59-
onChangeSelectedOption?: (option: SelectItemObject<T, R>) => void
6065
value?: T
61-
defaultItem?: SelectItemObject<T, R>
66+
defaultItem?: SelectItemObject<T, ResolvedRecordType<R>>
6267
children?: React.ReactNode
6368
open?: boolean
6469
showSearchBox?: boolean
@@ -73,18 +78,20 @@ export type SelectProps<T extends string, R extends RecordType = RecordType> = {
7378
} & (
7479
| {
7580
source: DataSourceDefinition<
76-
R,
81+
ResolvedRecordType<R>,
7782
FiltersDefinition,
7883
SortingsDefinition,
79-
GroupingDefinition<R>
84+
GroupingDefinition<ResolvedRecordType<R>>
8085
>
81-
mapOptions: (item: R) => SelectItemProps<T, R>
86+
mapOptions: (
87+
item: ResolvedRecordType<R>
88+
) => SelectItemProps<T, ResolvedRecordType<R>>
8289
options?: never
8390
}
8491
| {
8592
source?: never
8693
mapOptions?: never
87-
options: SelectItemProps<T, R>[]
94+
options: SelectItemProps<T, ResolvedRecordType<R>>[]
8895
}
8996
) &
9097
Pick<
@@ -154,7 +161,7 @@ const SelectValue = forwardRef<
154161

155162
const SelectComponent = forwardRef(function Select<
156163
T extends string,
157-
R extends RecordType = RecordType,
164+
R = unknown,
158165
>(
159166
{
160167
placeholder,
@@ -189,6 +196,7 @@ const SelectComponent = forwardRef(function Select<
189196
}: SelectProps<T, R>,
190197
ref: React.ForwardedRef<HTMLButtonElement>
191198
) {
199+
type ActualRecordType = ResolvedRecordType<R>
192200
const searchInputRef = useRef<HTMLInputElement>(null)
193201

194202
const [openLocal, setOpenLocal] = useState(open)
@@ -212,20 +220,23 @@ const SelectComponent = forwardRef(function Select<
212220
return {
213221
...source,
214222
dataAdapter: source
215-
? (source.dataAdapter as PaginatedDataAdapter<R, FiltersDefinition>)
223+
? (source.dataAdapter as PaginatedDataAdapter<
224+
ActualRecordType,
225+
FiltersDefinition
226+
>)
216227
: {
217228
fetchData: ({
218229
search,
219230
}: BaseFetchOptions<FiltersDefinition>): PromiseOrObservable<
220-
BaseResponse<R>
231+
BaseResponse<ActualRecordType>
221232
> => {
222233
return {
223234
records: options.filter(
224235
(option) =>
225236
option.type === "separator" ||
226237
!search ||
227238
option.label.toLowerCase().includes(search.toLowerCase())
228-
) as unknown as R[],
239+
) as unknown as ActualRecordType[],
229240
}
230241
},
231242
},
@@ -246,29 +257,29 @@ const SelectComponent = forwardRef(function Select<
246257
)
247258

248259
/**
249-
* Maps an item to a SelectItemProps<T, R>
260+
* Maps an item to a SelectItemProps<T, ActualRecordType>
250261
*/
251262
const optionMapper = useCallback(
252-
(item: R): SelectItemProps<T, R> => {
263+
(item: ActualRecordType): SelectItemProps<T, ActualRecordType> => {
253264
if (source) {
254265
if (!mapOptions) {
255266
throw new Error("mapOptions is required when using a source")
256267
}
257268
return mapOptions(item)
258269
}
259-
// At this point, we are sure that options is an array of SelectItemProps<T, R>
260-
return item as unknown as SelectItemProps<T, R>
270+
// At this point, we are sure that options is an array of SelectItemProps<T, ActualRecordType>
271+
return item as unknown as SelectItemProps<T, ActualRecordType>
261272
},
262273
[mapOptions, source]
263274
)
264275

265276
const { data, isInitialLoading, loadMore, isLoadingMore } =
266-
useData<R>(localSource)
277+
useData<ActualRecordType>(localSource)
267278

268279
const { currentSearch, setCurrentSearch } = localSource
269280

270281
const [selectedOption, setSelectedOption] = useState<
271-
SelectItemObject<T, R> | undefined
282+
SelectItemObject<T, ActualRecordType> | undefined
272283
>(undefined)
273284

274285
/**
@@ -277,7 +288,9 @@ const SelectComponent = forwardRef(function Select<
277288
* @returns The option if found, undefined otherwise
278289
*/
279290
const findOption = useCallback(
280-
(value: string | T | undefined): SelectItemObject<T, R> | undefined => {
291+
(
292+
value: string | T | undefined
293+
): SelectItemObject<T, ActualRecordType> | undefined => {
281294
if (value === undefined) {
282295
return undefined
283296
}
@@ -351,7 +364,9 @@ const SelectComponent = forwardRef(function Select<
351364
)
352365

353366
const getItems = useCallback(
354-
(records: WithGroupId<R>[] | R[]): VirtualItem[] => {
367+
(
368+
records: WithGroupId<ActualRecordType>[] | ActualRecordType[]
369+
): VirtualItem[] => {
355370
return records.map((option, index) => {
356371
const mappedOption = optionMapper(option)
357372
return mappedOption.type === "separator"
@@ -519,7 +534,7 @@ const SelectComponent = forwardRef(function Select<
519534

520535
export const Select = SelectComponent as <
521536
T extends string = string,
522-
R extends RecordType = RecordType,
537+
R = unknown,
523538
>(
524539
props: SelectProps<T, R> & { ref?: React.Ref<HTMLButtonElement> }
525540
) => React.ReactElement

packages/react/src/experimental/Navigation/Header/Breadcrumbs/internal/BreadcrumbSelect/index.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
import { F0Icon } from "@/components/F0Icon"
22
import {
3+
ResolvedRecordType,
34
Select,
45
SelectItemObject,
56
SelectProps,
67
} from "@/experimental/Forms/Fields/Select"
7-
import { RecordType } from "@/hooks/datasource"
88
import { ChevronDown } from "@/icons/app"
99
import { motion } from "motion/react"
1010
import { useState } from "react"
1111

12-
export type BreadcrumbSelectProps<
13-
T extends string,
14-
R extends RecordType = RecordType,
15-
> = SelectProps<T, R>
12+
export type BreadcrumbSelectProps<T extends string, R = unknown> = SelectProps<
13+
T,
14+
R
15+
>
1616

17-
export function BreadcrumbSelect<
18-
T extends string,
19-
R extends RecordType = RecordType,
20-
>({ ...props }: BreadcrumbSelectProps<T, R>) {
17+
export function BreadcrumbSelect<T extends string, R = unknown>({
18+
...props
19+
}: BreadcrumbSelectProps<T, R>) {
2120
const [localOpen, setLocalOpen] = useState(props.open)
2221

2322
const onOpenChangeLocal = (open: boolean) => {
@@ -31,13 +30,15 @@ export function BreadcrumbSelect<
3130

3231
const handleChange = (
3332
value: T,
34-
item?: R,
35-
option?: SelectItemObject<T, R>
33+
item?: ResolvedRecordType<R>,
34+
option?: SelectItemObject<T, ResolvedRecordType<R>>
3635
) => {
3736
props.onChange?.(value, item, option)
3837
}
3938

40-
const handleChangeSelectedOption = (option: SelectItemObject<T, R>) => {
39+
const handleChangeSelectedOption = (
40+
option: SelectItemObject<T, ResolvedRecordType<R>>
41+
) => {
4142
setSelectedLabel(option.label)
4243
}
4344

0 commit comments

Comments
 (0)