Skip to content

Commit 1e518d5

Browse files
refactor(multiselect): to use strings instead of SelectionItem
1 parent f2cfa1c commit 1e518d5

File tree

2 files changed

+61
-54
lines changed

2 files changed

+61
-54
lines changed

lib/input/select/Multiselect.tsx

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,32 @@
11
import { mdiCheck, mdiClose, mdiPlus } from "@mdi/js"
22
import { Key } from "@solid-primitives/keyed"
3+
import type { Accessor } from "solid-js"
34
import { mergeProps } from "solid-js"
45
import { ct0, ct1 } from "~ui/i18n/ct0"
56
import { t4multiselect } from "~ui/input/select/t4multiselect"
67
import { buttonVariant } from "~ui/interactive/button/buttonCva"
78
import { ButtonIcon } from "~ui/interactive/button/ButtonIcon"
8-
import { CorvuPopover, type CorvuPopoverProps } from "~ui/interactive/popover/CorvuPopover"
9+
import type { CorvuPopoverProps } from "~ui/interactive/popover/CorvuPopover"
10+
import { CorvuPopover } from "~ui/interactive/popover/CorvuPopover"
911
import { classArr } from "~ui/utils/classArr"
1012
import { classMerge } from "~ui/utils/classMerge"
1113
import type { SignalObject } from "~ui/utils/createSignalObject"
12-
import type { HasGetOptions } from "~ui/utils/HasGetOptions"
1314
import type { MayHaveChildren } from "~ui/utils/MayHaveChildren"
1415
import type { MayHaveClass } from "~ui/utils/MayHaveClass"
15-
import type { SelectionItem } from "~ui/utils/SelectionItem"
1616

1717
/**
1818
* https://github.com/radix-ui/primitives/blob/main/packages/react/checkbox/src/Checkbox.tsx
1919
*/
20-
export interface MultiselectProps<T extends string = string> extends MultiselectState<T>, MayHaveClass, MayHaveChildren {
20+
export interface Multiselect2Props extends MayHaveClass, MayHaveChildren {
2121
buttonProps: CorvuPopoverProps
2222
textNoEntries?: string
2323
textAddEntry?: string
24+
getOptions: Accessor<string[]>
25+
valueSignal: SignalObject<string[]>
26+
valueDisplay?: (value: string) => string
2427
}
2528

26-
export interface MultiselectState<T extends string = string>
27-
extends MultiselectStateValue<T>,
28-
MultiselectStateOptions<T> {}
29-
30-
export type MultiselectStateValue<T extends string = string> = {
31-
valueSignal: SignalObject<SelectionItem<T>[]>
32-
}
33-
34-
export type MultiselectStateOptions<T extends string = string> = HasGetOptions<T>
35-
36-
export function Multiselect<T extends string = string>(p: MultiselectProps<T>) {
29+
export function Multiselect(p: Multiselect2Props) {
3730
const buttonProps = mergeProps(
3831
{
3932
icon: mdiPlus,
@@ -53,102 +46,112 @@ export function Multiselect<T extends string = string>(p: MultiselectProps<T>) {
5346
p.class,
5447
)}
5548
>
56-
<SelectedValues valueSignal={p.valueSignal} />
49+
<SelectedValues2 valueSignal={p.valueSignal} valueDisplay={p.valueDisplay} />
5750
<CorvuPopover {...buttonProps}>
5851
<div class={classArr("bg-white dark:bg-black", "max-h-dvh", "grid grid-cols-3 gap-x-2 gap-y-1")}>
59-
<OptionList valueSignal={p.valueSignal} getOptions={p.getOptions} />
52+
<OptionList2 valueSignal={p.valueSignal} getOptions={p.getOptions} valueDisplay={p.valueDisplay} />
6053
</div>
6154
</CorvuPopover>
6255
</div>
6356
)
6457
}
6558

66-
type MultiselectOptionState<T extends string = string> = { option: SelectionItem<T> } & MultiselectStateValue<T>
59+
interface Multiselect2OptionState {
60+
option: string
61+
}
62+
63+
interface Multiselect2OptionState {
64+
valueSignal: SignalObject<string[]>
65+
valueDisplay?: (value: string) => string
66+
}
6767

68-
function SelectedValues<T extends string = string>(p: MultiselectStateValue<T>) {
69-
// items-center justify-center
68+
function SelectedValues2(p: { valueSignal: SignalObject<string[]>; valueDisplay?: (value: string) => string }) {
7069
return (
7170
<div class={"flex flex-wrap gap-1"}>
72-
<Key each={p.valueSignal.get()} by={(item) => item.value} fallback={<NoItems />}>
73-
{(item) => <SelectedValue option={item()} valueSignal={p.valueSignal} />}
71+
<Key each={p.valueSignal.get()} by={(item) => item} fallback={<NoItems2 />}>
72+
{(item) => <SelectedValue2 option={item()} valueSignal={p.valueSignal} valueDisplay={p.valueDisplay} />}
7473
</Key>
7574
</div>
7675
)
7776
}
7877

79-
function SelectedValue<T extends string = string>(p: MultiselectOptionState<T>) {
78+
function SelectedValue2(p: Multiselect2OptionState) {
79+
const label = () => (p.valueDisplay ? p.valueDisplay(p.option) : p.option)
8080
return (
8181
<ButtonIcon
8282
variant={buttonVariant.outline}
8383
iconRight={mdiClose}
8484
class={"text-sm px-2 py-1"}
85-
data-value={p.option.value}
86-
onMouseDown={(e) => optionRemove(p)}
87-
onClick={(e) => optionRemove(p)}
88-
title={ct1(t4multiselect.Remove_x, p.option.label)}
85+
data-value={p.option}
86+
onMouseDown={(e) => optionRemove2(p)}
87+
onClick={(e) => optionRemove2(p)}
88+
title={ct1(t4multiselect.Remove_x, label())}
8989
>
90-
{p.option.label}
90+
{label()}
9191
</ButtonIcon>
9292
)
9393
}
9494

95-
function OptionList<T extends string = string>(p: MultiselectState<T>) {
95+
function OptionList2(p: {
96+
valueSignal: SignalObject<string[]>
97+
getOptions: Accessor<string[]>
98+
valueDisplay?: (value: string) => string
99+
}) {
96100
return (
97101
<>
98-
<Key each={p.getOptions()} by={(item) => item.value} fallback={<NoItems />}>
99-
{(item) => <ListOption option={item()} valueSignal={p.valueSignal} />}
102+
<Key each={p.getOptions()} by={(item) => item} fallback={<NoItems2 />}>
103+
{(item) => <ListOption2 option={item()} valueSignal={p.valueSignal} valueDisplay={p.valueDisplay} />}
100104
</Key>
101105
</>
102106
)
103107
}
104108

105-
function ListOption<T extends string = string>(p: MultiselectOptionState<T>) {
109+
function ListOption2(p: Multiselect2OptionState) {
110+
const label = () => (p.valueDisplay ? p.valueDisplay(p.option) : p.option)
106111
return (
107112
<>
108113
<ButtonIcon
109114
type="button"
110115
role="checkbox"
111-
aria-checked={optionIsSelected(p)}
112-
data-state={optionIsSelected(p)}
113-
iconRight={optionIsSelected(p) ? mdiCheck : undefined}
116+
aria-checked={optionIsSelected2(p)}
117+
data-state={optionIsSelected2(p)}
118+
iconRight={optionIsSelected2(p) ? mdiCheck : undefined}
114119
onClick={(e) => {
115-
// console.log("onchange", e.currentTarget.id, e.currentTarget.checked)
116-
toggleOption(p)
120+
toggleOption2(p)
117121
}}
118122
variant={buttonVariant.ghost}
119-
// class={optionIsSelected(p.option, p.valueSignal) ? "" : "pl-8"}
120123
class={"justify-start"}
121124
>
122-
{p.option.label}
125+
{label()}
123126
</ButtonIcon>
124127
</>
125128
)
126129
}
127130

128-
function toggleOption<T extends string = string>(p: MultiselectOptionState<T>) {
129-
const hasOption = optionIsSelected(p)
131+
function toggleOption2(p: Multiselect2OptionState) {
132+
const hasOption = optionIsSelected2(p)
130133
if (hasOption) {
131-
return optionRemove(p)
134+
return optionRemove2(p)
132135
}
133-
return optionAdd(p)
136+
return optionAdd2(p)
134137
}
135138

136-
function optionRemove<T extends string = string>(p: MultiselectOptionState<T>) {
137-
const newValues = p.valueSignal.get().filter((v) => v.value !== p.option.value)
139+
function optionRemove2(p: Multiselect2OptionState) {
140+
const newValues = p.valueSignal.get().filter((v) => v !== p.option)
138141
p.valueSignal.set(newValues)
139142
}
140143

141-
function optionAdd<T extends string = string>(p: MultiselectOptionState<T>) {
144+
function optionAdd2(p: Multiselect2OptionState) {
142145
const newValues = [...p.valueSignal.get(), p.option]
143-
newValues.sort((a, b) => a.label.localeCompare(b.label))
146+
newValues.sort((a, b) => a.localeCompare(b))
144147
p.valueSignal.set(newValues)
145148
}
146149

147-
function optionIsSelected<T extends string = string>(p: MultiselectOptionState<T>) {
148-
return p.valueSignal.get().some((v) => v.value === p.option.value)
150+
function optionIsSelected2(p: Multiselect2OptionState) {
151+
return p.valueSignal.get().includes(p.option)
149152
}
150153

151-
function NoItems(p: MayHaveClass) {
154+
function NoItems2(p: MayHaveClass = {}) {
152155
return (
153156
<div
154157
class={classMerge(
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { Multiselect } from "~ui/input/select/Multiselect"
22
import { PageWrapper } from "~ui/static/page/PageWrapper"
3-
import type { SelectionItem } from "~ui/utils/SelectionItem"
43
import { createSignalObject } from "~ui/utils/createSignalObject"
54
import { arrCreate } from "~utils/arr/arrCreate"
65

7-
const options100 = arrCreate<SelectionItem>(100, (i) => ({ value: "" + i, label: "Option " + i }))
8-
const multiValueSignal = createSignalObject<SelectionItem[]>([])
6+
const options100Strings = arrCreate<string>(100, (i) => "" + i)
7+
const multiValueSignal = createSignalObject<string[]>([])
98

109
export function DemoMultiSelect() {
1110
return (
1211
<PageWrapper>
13-
<Multiselect valueSignal={multiValueSignal} getOptions={() => options100} buttonProps={{}} />
12+
<Multiselect
13+
valueSignal={multiValueSignal}
14+
getOptions={() => options100Strings}
15+
buttonProps={{}}
16+
valueDisplay={(value) => "Option " + value}
17+
/>
1418
</PageWrapper>
1519
)
1620
}

0 commit comments

Comments
 (0)