Skip to content

Commit 709c371

Browse files
committed
refactor: improve ComboboxSelect component and usage
1 parent 4ae5179 commit 709c371

File tree

2 files changed

+30
-141
lines changed

2 files changed

+30
-141
lines changed
Lines changed: 27 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,74 @@
1-
import { useEffect, useState } from 'react';
1+
import { useEffect, useMemo, type ComponentProps } from 'react';
22
import { Combobox, HStack, Portal, Span, Spinner, Skeleton } from '@chakra-ui/react';
33
import { useFilter, useListCollection } from '@chakra-ui/react';
44

5-
type EndpointOption = {
5+
type Option = {
66
label: string;
77
value: string;
88
};
99

10-
interface ComboboxProps {
11-
items: { label: string; value: string }[];
12-
selectedValue: string;
10+
interface ComboboxProps extends Omit<ComponentProps<typeof Combobox.Root>, 'onChange' | 'children' | 'value' | 'collection'
11+
> {
12+
items: Option[];
13+
selectedValue?: string;
1314
onChange: (value: string) => void;
14-
isLoading?: boolean;
15-
error?: boolean;
1615
placeholder?: string;
17-
optionName?: string;
18-
defaultToFirst?: boolean;
16+
isLoading?: boolean;
1917
}
2018

2119
export function ComboboxSelect({
2220
items,
23-
selectedValue,
21+
selectedValue = '',
2422
onChange,
23+
placeholder = 'Select an option',
2524
isLoading = false,
26-
error = false,
27-
placeholder = 'Select endpoint...',
28-
optionName = '',
29-
defaultToFirst = false,
25+
...rest
3026
}: ComboboxProps) {
31-
const [cleared, setCleared] = useState(false);
32-
33-
const itemsWithAll: EndpointOption[] =
34-
defaultToFirst && items.length > 0
35-
? items
36-
: [{ label: `All ${optionName}`, value: '' }, ...items];
3727

38-
console.log('Items with All option:', itemsWithAll);
28+
const itemsWithAll = useMemo(() => {
29+
return items.length > 0
30+
? [{ label: 'All', value: '' }, ...items]
31+
: items;
32+
}, [items]);
3933

40-
// Filtering
4134
const { contains } = useFilter({ sensitivity: 'base' });
42-
const { collection, set, filter } = useListCollection<EndpointOption>({
35+
const { collection, set, filter } = useListCollection<Option>({
4336
initialItems: [],
4437
itemToString: item => item.label,
4538
itemToValue: item => item.value,
4639
filter: contains,
4740
});
4841

49-
// Update collection whenever items change
5042
useEffect(() => {
5143
set(itemsWithAll);
52-
53-
if (defaultToFirst && itemsWithAll.length > 0 && !selectedValue && !cleared) {
54-
console.log('Defaulting to first item:', itemsWithAll[0]);
55-
onChange(itemsWithAll[0].value);
56-
57-
} else if (!defaultToFirst && !selectedValue && !cleared) {
58-
console.log('Defaulting to "All" option');
59-
onChange('');
60-
}
61-
}, [items, set, selectedValue, cleared, onChange]);
44+
}, [itemsWithAll, set]);
6245

6346
return (
6447
<Skeleton loading={isLoading} w='md'>
6548
<Combobox.Root
66-
size='xs'
49+
size='md'
6750
w='xs'
6851
collection={collection}
69-
// value={selectedValue ? [selectedValue] : []}
7052
value={[selectedValue]}
7153
onValueChange={e => {
72-
onChange(e.value[0] ?? '');
73-
setCleared(false);
54+
const newValue = e.value[0] ?? '';
55+
onChange(newValue);
7456
}}
7557
onInputValueChange={e => filter(e.inputValue)}
7658
onOpenChange={open => {
7759
if (open) filter('');
7860
}}
7961
openOnClick
62+
{...rest}
8063
>
8164
<Combobox.Control>
82-
<Combobox.Input placeholder={placeholder} />
65+
<Combobox.Input
66+
placeholder={placeholder || 'Select an option'}
67+
/>
8368
<Combobox.IndicatorGroup>
8469
<Combobox.ClearTrigger
8570
onClick={() => {
8671
onChange('');
87-
setCleared(true);
8872
}}
8973
/>
9074
<Combobox.Trigger />
@@ -96,14 +80,10 @@ export function ComboboxSelect({
9680
{isLoading ? (
9781
<HStack p='2'>
9882
<Spinner size='xs' borderWidth='1px' />
99-
<Span>Loading endpoints...</Span>
83+
<Span>Loading...</Span>
10084
</HStack>
101-
) : error ? (
102-
<Span p='2' color='fg.error'>
103-
Failed to load endpoints
104-
</Span>
10585
) : collection.items.length === 0 ? (
106-
<Combobox.Empty>No endpoints found</Combobox.Empty>
86+
<Combobox.Empty>No options found</Combobox.Empty>
10787
) : (
10888
collection.items.map(item => (
10989
<Combobox.Item key={item.value} item={item}>
@@ -120,4 +100,4 @@ export function ComboboxSelect({
120100
</Combobox.Root>
121101
</Skeleton>
122102
);
123-
}
103+
}

thingconnect.pulse.client/src/components/status/StatusFilters.tsx

Lines changed: 3 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@ import {
77
Input,
88
Text,
99
Menu,
10-
Combobox,
11-
useFilter,
12-
useListCollection,
1310
} from '@chakra-ui/react';
1411
import { X } from 'lucide-react';
1512
import type { LiveStatusParams } from '@/api/types';
1613
import { MdSearch, MdExpandMore } from 'react-icons/md';
17-
import { useEffect, useState } from 'react';
14+
import { ComboboxSelect } from '../common/ComboboxSelect';
1815
// import { EndpointCombobox } from '../common/ComboboxSelect';
1916

2017
interface StatusFiltersProps {
@@ -44,28 +41,6 @@ export function StatusFilters({
4441
selectedGroup,
4542
onSelectedGroupChange,
4643
}: StatusFiltersProps) {
47-
// const [cleared, setCleared] = useState(false);
48-
49-
// const { contains } = useFilter({ sensitivity: 'base' });
50-
// const { collection, filter, set } = useListCollection<{
51-
// label: string;
52-
// value: string;
53-
// }>({
54-
// initialItems: [], // start empty
55-
// itemToString: item => item.label,
56-
// itemToValue: item => item.value,
57-
// filter: contains,
58-
// });
59-
60-
// useEffect(() => {
61-
// const newItems = groups.map(g => ({
62-
// label: g.name,
63-
// value: g.id,
64-
// }));
65-
66-
// set(newItems);
67-
// }, [groups, set, cleared]);
68-
6944
const handleGroupChange = (value: string) => {
7045
const newGroup = value || undefined;
7146
onSelectedGroupChange?.(newGroup);
@@ -76,10 +51,8 @@ export function StatusFilters({
7651
};
7752

7853
const handleSearchChange = (value: string) => {
79-
// Update search term in parent component
8054
onSearchChange && onSearchChange(value);
8155

82-
// Optionally reset page when searching
8356
onFiltersChange({
8457
...filters,
8558
search: value || undefined,
@@ -88,19 +61,9 @@ export function StatusFilters({
8861

8962
const clearSearch = () => {
9063
onSearchChange?.('');
91-
// Optional: reset local cleared state
64+
9265
};
9366

94-
// const clearFilter = () => {
95-
// onFiltersChange({
96-
// ...filters,
97-
// group: undefined,
98-
// search: undefined,
99-
// });
100-
// onSelectedGroupChange?.(undefined);
101-
// setCleared(true);
102-
// };
103-
10467
return (
10568
<Box
10669
position={{ base: 'sticky', md: 'static' }}
@@ -124,61 +87,7 @@ export function StatusFilters({
12487
>
12588
<HStack gap={{ base: 2, md: 4 }} align='center' flexWrap={{ base: 'wrap', md: 'nowrap' }}>
12689
{/* Group Filter */}
127-
<Box w={'25%'}>
128-
{/* <Combobox.Root
129-
size='md'
130-
collection={collection}
131-
value={selectedGroup ? [selectedGroup] : []}
132-
onValueChange={e => {
133-
handleGroupChange(e.value[0]);
134-
setCleared(false);
135-
}}
136-
onInputValueChange={e => {
137-
filter(e.inputValue);
138-
}}
139-
onOpenChange={open => {
140-
if (open) filter('');
141-
}}
142-
openOnClick
143-
>
144-
<Combobox.Control>
145-
<Combobox.Input placeholder='Select Group...' />
146-
<Combobox.IndicatorGroup>
147-
<Combobox.ClearTrigger onClick={clearFilter} cursor={'pointer'} />
148-
<Combobox.Trigger />
149-
</Combobox.IndicatorGroup>
150-
</Combobox.Control>
151-
<Combobox.Positioner>
152-
<Combobox.Content>
153-
<Combobox.Empty>No groups found</Combobox.Empty>
154-
<Combobox.Item key='allgroups' item={{ label: 'All Groups', value: '' }}>
155-
<HStack justify='space-between' textStyle='sm'>
156-
All Groups
157-
</HStack>
158-
</Combobox.Item>
159-
{collection.items.map(item => {
160-
return (
161-
<Combobox.Item key={item.value} item={item}>
162-
<HStack justify='space-between' textStyle='sm'>
163-
{item.label}
164-
</HStack>
165-
</Combobox.Item>
166-
);
167-
})}
168-
</Combobox.Content>
169-
</Combobox.Positioner>
170-
</Combobox.Root> */}
171-
{/* <EndpointCombobox
172-
items={groups.map(g => ({ label: g.name, value: g.id }))}
173-
selectedValue={selectedGroup ? selectedGroup : ''}
174-
onChange={handleGroupChange}
175-
isLoading={false}
176-
error={undefined}
177-
placeholder='Select Group...'
178-
optionName='Groups'
179-
defaultToFirst={false}
180-
/> */}
181-
</Box>
90+
18291
{/* Search Input */}
18392
<Flex w='80' position='relative' align='center'>
18493
<Icon

0 commit comments

Comments
 (0)