Skip to content

Commit

Permalink
feat: bot select component
Browse files Browse the repository at this point in the history
  • Loading branch information
ddiu8081 committed May 1, 2023
1 parent ee5e962 commit bbbb916
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 33 deletions.
33 changes: 28 additions & 5 deletions src/components/ui/BotSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { createSignal } from 'solid-js'
import { providerMetaList } from '@/stores/provider'
import { Select } from '../ui/base'
import type { ConversationType } from '@/types/conversation'

interface BotMeta {
id: string
value: string
type: ConversationType
name: string
label: string
provider: {
id: string
name: string
Expand All @@ -14,9 +16,9 @@ interface BotMeta {

const botMetaList: BotMeta[] = providerMetaList.map((provider) => {
return provider.bots.map(bot => ({
id: bot.id,
value: bot.id,
type: bot.type,
name: bot.name,
label: bot.name,
provider: {
id: provider.id,
name: provider.name,
Expand All @@ -26,7 +28,28 @@ const botMetaList: BotMeta[] = providerMetaList.map((provider) => {
}).flat()

export default () => {
const [value, setValue] = createSignal('')

return (
<div>{JSON.stringify(botMetaList)}</div>
<Select
value={value()}
onChange={setValue}
options={botMetaList}
selectedComponent={item => (
<div class="fi gap-2">
{item.provider.icon && <div class={item.provider.icon} />}
<div>{item.provider.name} / {item.label}</div>
</div>
)}
itemComponent={(item, isSelected) => (
<div class="fi gap-2 w-full px-2 py-1 border-b border-b-base hv-base">
{item.provider.icon && <div class={item.provider.icon} />}
<div class="flex-1">{item.provider.name} / {item.label}</div>
{isSelected && (
<div i-carbon-checkmark />
)}
</div>
)}
/>
)
}
2 changes: 1 addition & 1 deletion src/components/ui/SettingsSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default ({ settings, editing, value, setValue }: Props) => {
return (
<div>
{editing() && (
<Select value={value} setValue={setValue} options={selectSettings.options} />
<Select value={value()} onChange={setValue} options={selectSettings.options} />
)}
{!editing() && value() && (
<div>{value()}</div>
Expand Down
68 changes: 42 additions & 26 deletions src/components/ui/base/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,81 @@
import { createEffect, createMemo, createUniqueId, mergeProps, on } from 'solid-js'
import * as select from '@zag-js/select'
import { normalizeProps, useMachine } from '@zag-js/solid'
import type { JSXElement } from 'solid-js'
import type { SelectOptionType } from '@/types/provider'
import type { Accessor } from 'solid-js'

interface Props {
options: SelectOptionType[]
value: Accessor<string>
setValue: (v: string) => void
interface Props<T> {
options: T[]
value: string
onChange: (v: string) => void
placeholder?: string
readonly?: boolean
selectedComponent?: (item: T) => JSXElement
itemComponent?: (item: T, isSelected: boolean) => JSXElement
}

export const Select = (inputProps: Props) => {
export const Select = <T extends SelectOptionType>(inputProps: Props<T>) => {
const props = mergeProps({
placeholder: 'Select option',
}, inputProps)
const [state, send] = useMachine(select.machine({
id: createUniqueId(),
selectedOption: props.options.find(o => o.value === props.value()),
selectedOption: props.options.find(o => o.value === props.value),
readOnly: props.readonly,
onChange: (details) => {
details && props.setValue(details.value)
details && props.onChange(details.value)
},
}))

const api = createMemo(() => select.connect(state, send, normalizeProps))

createEffect(on(props.value, () => {
const option = props.options.find(o => o.value === props.value())
createEffect(on(() => props.value, () => {
const option = props.options.find(o => o.value === props.value)
option && api().setSelectedOption(option)
}))

const selectedComponent = (item: T | null) => {
if (!item) return <div>{props.placeholder}</div>
if (props.selectedComponent) return props.selectedComponent(item)
return (
<div class="fi gap-2">
{item?.icon && <div class={item.icon} />}
<div>{item.label ?? props.placeholder}</div>
</div>
)
}

const itemComponent = (item: T, isSelected: boolean) => {
if (props.itemComponent) return props.itemComponent(item, isSelected)
return (
<div class="fi justify-between w-full px-2 py-1 border-b border-b-base hv-base">
<div class="fi gap-2">
{item.icon && <div class={item.icon} />}
<div>{item.label}</div>
</div>
{isSelected && (
<div i-carbon-checkmark />
)}
</div>
)
}

return (
<div>
<div>
<button
class={`fi justify-between w-full px-2 py-1 border border-base ${props.readonly ? '' : 'hv-base'}`}
{...api().triggerProps}
>
<div class="fi gap-2">
{(api().selectedOption as SelectOptionType)?.icon && <div class={(api().selectedOption as SelectOptionType)?.icon} />}
<div>{api().selectedOption?.label ?? props.placeholder}</div>
</div>
{selectedComponent(api().selectedOption as T)}
{!props.readonly && <div i-carbon-caret-down />}
</button>
</div>
<div class="w-$reference-width -mt-2 z-100 shadow-md" {...api().positionerProps}>
<ul class="bg-base" {...api().contentProps}>
{props.options.map(({ label, value, icon }) => (
<li
class="fi justify-between w-full px-2 py-1 border-b border-b-base hv-base"
{...api().getOptionProps({ label, value })}
>
<div class="fi gap-2">
{icon && <div class={icon} />}
<div>{label}</div>
</div>
{value === api().selectedOption?.value && (
<div i-carbon-checkmark />
)}
{props.options.map(item => (
<li {...api().getOptionProps({ label: item.label, value: item.value })}>
{itemComponent(item, item.value === api().selectedOption?.value)}
</li>
))}
</ul>
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { title } = Astro.props;
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
<link rel="mask-icon" href="/icon.svg" color="#FFFFFF" />
Expand Down

0 comments on commit bbbb916

Please sign in to comment.