Skip to content

Commit 1cba5d5

Browse files
feat: Refactor dropdown select component using HeroUI (#289)
2 parents d5279fd + 3e371a1 commit 1cba5d5

File tree

6 files changed

+126
-131
lines changed

6 files changed

+126
-131
lines changed

README-zh.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@
4848
```bash
4949
npx drizzle-kit generate
5050
```
51+
5152
5. **构建 MCP Streamable Http**
53+
5254
```bash
5355
cd packages/react-native-streamable-http
5456
npm install
5557
npm run build
5658
```
59+
5760
6. **启动应用程序**
5861

5962
iOS:

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ English | [中文](./README-zh.md)
4848
```bash
4949
npx drizzle-kit generate
5050
```
51+
5152
5. **Build the MCP Streamable Http**
53+
5254
```bash
5355
cd packages/react-native-streamable-http
5456
npm install
5557
npm run build
5658
```
59+
5760
6. **Start the application**
5861

5962
iOS:

src/componentsV2/base/SelectionDropdown/index.tsx

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,71 @@
1+
import { Select } from 'heroui-native'
2+
import type { FC } from 'react'
13
import React from 'react'
24
import type { SFSymbol } from 'sf-symbols-typescript'
3-
import * as DropdownMenu from 'zeego/dropdown-menu'
45

5-
import { isIOS } from '@/utils/device'
6+
interface SelectOption {
7+
value: string
8+
label: string
9+
}
610

711
export interface SelectionDropdownItem {
812
id?: string
913
key?: string
1014
label: React.ReactNode | string
11-
description?: React.ReactNode | string
12-
icon?: React.ReactNode | ((isSelected: boolean) => React.ReactNode)
15+
icon?: React.ReactNode
1316
iOSIcon?: SFSymbol | string
1417
isSelected?: boolean
15-
onSelect?: () => void
1618
destructive?: boolean
17-
[x: string]: any
19+
onSelect?: () => void
1820
}
1921

2022
export interface SelectionDropdownProps {
2123
items: SelectionDropdownItem[]
2224
children: React.ReactNode
2325
shouldDismissMenuOnSelect?: boolean
26+
onValueChange?: (value: string) => void
2427
}
2528

26-
/**
27-
* 用于显示下拉选择菜单的组件
28-
* 使用 Zeego dropdown menu 实现,支持原生体验
29-
*/
30-
const SelectionDropdown: React.FC<SelectionDropdownProps> = ({ items, children, shouldDismissMenuOnSelect = true }) => {
29+
const SelectionDropdown: FC<SelectionDropdownProps> = ({
30+
items,
31+
children,
32+
shouldDismissMenuOnSelect = true,
33+
onValueChange
34+
}) => {
35+
const handleValueChange = (selectedItem: SelectOption | undefined) => {
36+
if (!selectedItem) {
37+
return
38+
}
39+
40+
const newValue = selectedItem.value
41+
42+
if (onValueChange) {
43+
onValueChange(newValue)
44+
}
45+
46+
const foundItem = items.find(item => item.id === newValue || item.key === newValue)
47+
if (foundItem && foundItem.onSelect) {
48+
foundItem.onSelect()
49+
}
50+
}
51+
52+
const renderItems = () =>
53+
items.map((item, index) => {
54+
const itemValue = item.id || item.key || String(index)
55+
const itemLabel = typeof item.label === 'string' ? item.label : `Item ${index + 1}`
56+
return <Select.Item key={itemValue} value={itemValue} label={itemLabel} />
57+
})
58+
3159
return (
32-
<DropdownMenu.Root>
33-
<DropdownMenu.Trigger>{children}</DropdownMenu.Trigger>
34-
35-
<DropdownMenu.Content>
36-
{items.map((item, index) => {
37-
const itemKey = item.key?.toString() || item.id?.toString() || index.toString()
38-
const iconElement = typeof item.icon === 'function' ? item.icon(item.isSelected ?? false) : item.icon
39-
40-
return (
41-
<DropdownMenu.CheckboxItem
42-
destructive={item.destructive}
43-
shouldDismissMenuOnSelect={shouldDismissMenuOnSelect}
44-
key={itemKey}
45-
value={item.isSelected ? 'on' : 'off'}
46-
onValueChange={() => item.onSelect?.()}>
47-
{isIOS && item.iOSIcon && <DropdownMenu.ItemIcon ios={{ name: item.iOSIcon }} />}
48-
{!isIOS && iconElement && <DropdownMenu.ItemIcon>{iconElement}</DropdownMenu.ItemIcon>}
49-
<DropdownMenu.ItemTitle>{item.label}</DropdownMenu.ItemTitle>
50-
</DropdownMenu.CheckboxItem>
51-
)
52-
})}
53-
</DropdownMenu.Content>
54-
</DropdownMenu.Root>
60+
<Select onValueChange={handleValueChange}>
61+
<Select.Trigger asChild>{children}</Select.Trigger>
62+
<Select.Portal>
63+
<Select.Overlay closeOnPress={shouldDismissMenuOnSelect} />
64+
<Select.Content width="trigger" placement="bottom" align="center">
65+
{renderItems()}
66+
</Select.Content>
67+
</Select.Portal>
68+
</Select>
5569
)
5670
}
5771

src/componentsV2/features/SettingsScreen/providers/ModelSelect.tsx

Lines changed: 15 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Button } from 'heroui-native'
22
import { sortBy } from 'lodash'
33
import React, { useState } from 'react'
44
import { useTranslation } from 'react-i18next'
5-
import * as DropdownMenu from 'zeego/dropdown-menu'
65

6+
import SelectionDropdown from '@/componentsV2/base/SelectionDropdown'
77
import { ChevronDown } from '@/componentsV2/icons'
88
import { isEmbeddingModel } from '@/config/models'
99
import type { Model, Provider } from '@/types/assistant'
@@ -20,54 +20,27 @@ export function ModelSelect({ provider, onSelectModel }: ModelSelectProps) {
2020

2121
const selectOptions = !provider.models?.length
2222
? []
23-
: [
24-
{
25-
label: provider.isSystem ? t(`provider.${provider.id}`) : provider.name,
26-
title: provider.name,
27-
options: sortBy(provider.models, 'name')
28-
.filter(model => !isEmbeddingModel(model))
29-
.map(model => ({
30-
label: model.name,
31-
value: getModelUniqId(model),
32-
model
33-
}))
34-
}
35-
]
23+
: sortBy(provider.models, 'name')
24+
.filter(model => !isEmbeddingModel(model))
25+
.map(model => ({
26+
id: getModelUniqId(model),
27+
label: model.name,
28+
model
29+
}))
3630

3731
const handleValueChange = (value: string) => {
38-
if (!value) {
39-
setSelectedModel(undefined)
40-
onSelectModel(undefined)
41-
return
42-
}
43-
44-
const allOptions = selectOptions.flatMap(group => group.options)
45-
const foundOption = allOptions.find(opt => opt.value === value)
32+
const foundOption = selectOptions.find(opt => opt.id === value)
4633
const model = foundOption?.model
4734
setSelectedModel(model)
4835
onSelectModel(model)
4936
}
5037

5138
return (
52-
<DropdownMenu.Root>
53-
<DropdownMenu.Trigger>
54-
<Button className="rounded-xl" pressableFeedbackVariant="ripple" variant="tertiary">
55-
<Button.Label>{selectedModel ? selectedModel.id : t('settings.provider.api_check.tooltip')}</Button.Label>
56-
<ChevronDown />
57-
</Button>
58-
</DropdownMenu.Trigger>
59-
<DropdownMenu.Content>
60-
{selectOptions.map(group => (
61-
<DropdownMenu.Group key={group.label}>
62-
<DropdownMenu.Label>{t(`${group.label}`)}</DropdownMenu.Label>
63-
{group.options.map(option => (
64-
<DropdownMenu.Item key={option.value} onSelect={() => handleValueChange(option.value)}>
65-
{option.label}
66-
</DropdownMenu.Item>
67-
))}
68-
</DropdownMenu.Group>
69-
))}
70-
</DropdownMenu.Content>
71-
</DropdownMenu.Root>
39+
<SelectionDropdown items={selectOptions} onValueChange={handleValueChange}>
40+
<Button className="rounded-xl" pressableFeedbackVariant="ripple" variant="tertiary">
41+
<Button.Label>{selectedModel ? selectedModel.id : t('settings.provider.api_check.tooltip')}</Button.Label>
42+
<ChevronDown />
43+
</Button>
44+
</SelectionDropdown>
7245
)
7346
}

src/componentsV2/features/SettingsScreen/providers/ProviderSelect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function ProviderSelect({ value, onValueChange, placeholder, className }:
6262
<Pressable className={className}>
6363
<Button
6464
pressableFeedbackVariant="ripple"
65-
className="h-8 justify-between rounded-lg"
65+
className="min-w-35 h-8 justify-between rounded-lg"
6666
variant="tertiary"
6767
size="sm"
6868
pointerEvents="none">

0 commit comments

Comments
 (0)