Skip to content

Commit 10fee3d

Browse files
feat(CheckMultiple): add multiple check component
1 parent 0f7b3ac commit 10fee3d

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed

lib/input/check/CheckMultiple.tsx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { mdiCheckboxBlankCircleOutline, mdiCheckboxMarkedCircle } from "@mdi/js"
2+
import type { Accessor } from "solid-js"
3+
import { For } from "solid-js"
4+
import { buttonVariant } from "~ui/interactive/button/buttonCva"
5+
import { ButtonIcon } from "~ui/interactive/button/ButtonIcon"
6+
import { classesGridCols3xl } from "~ui/static/container/classesGridCols"
7+
import { classArr } from "~ui/utils/classArr"
8+
import { classMerge } from "~ui/utils/classMerge"
9+
import type { SignalObject } from "~ui/utils/createSignalObject"
10+
import type { MayHaveButtonVariant } from "~ui/utils/MayHaveButtonVariant"
11+
import type { MayHaveClass } from "~ui/utils/MayHaveClass"
12+
import type { MayHaveId } from "~ui/utils/MayHaveId"
13+
import type { MayHaveInnerClass } from "~ui/utils/MayHaveInnerClass"
14+
15+
export interface CheckMultipleProps extends MayHaveId, MayHaveButtonVariant, MayHaveClass, MayHaveInnerClass {
16+
// content
17+
valueSignal: SignalObject<string[]>
18+
getOptions: Accessor<string[]>
19+
valueText?: (value: string) => string
20+
// styling
21+
optionClass?: string
22+
}
23+
24+
export function CheckMultiple(p: CheckMultipleProps) {
25+
return (
26+
<div
27+
id={p.id}
28+
class={classArr(
29+
"group border border-input",
30+
"px-2 py-2",
31+
"ring-offset-background",
32+
"rounded-md",
33+
"focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
34+
"flex flex-col items-center justify-center gap-1", // layout
35+
p.class,
36+
)}
37+
>
38+
<OptionList
39+
valueSignal={p.valueSignal}
40+
getOptions={p.getOptions}
41+
valueText={p.valueText}
42+
variant={p.variant}
43+
optionClass={p.optionClass}
44+
innerClass={p.innerClass}
45+
/>
46+
</div>
47+
)
48+
}
49+
50+
interface OptionListProps extends MayHaveButtonVariant, MayHaveInnerClass {
51+
valueSignal: SignalObject<string[]>
52+
getOptions: Accessor<string[]>
53+
valueText?: (value: string) => string
54+
optionClass?: string
55+
}
56+
57+
function OptionList(p: OptionListProps) {
58+
const innerClass = () => {
59+
if (p.innerClass) return p.innerClass
60+
const optionAmount = p.getOptions().length
61+
if (optionAmount <= 0) return ""
62+
const base = " gap-x-2 gap-y-1"
63+
if (optionAmount <= 5) return `grid grid-cols-1${base}`
64+
if (optionAmount <= 9) return `grid grid-cols-2${base}`
65+
return `${classesGridCols3xl}${base}`
66+
}
67+
68+
return (
69+
<div class={innerClass()}>
70+
<For each={p.getOptions()}>
71+
{(option) => (
72+
<CheckOption
73+
option={option}
74+
valueSignal={p.valueSignal}
75+
valueText={p.valueText}
76+
optionClass={p.optionClass}
77+
variant={p.variant}
78+
/>
79+
)}
80+
</For>
81+
</div>
82+
)
83+
}
84+
85+
interface CheckOptionProps extends MayHaveButtonVariant {
86+
option: string
87+
valueSignal: SignalObject<string[]>
88+
valueText?: (value: string) => string
89+
optionClass?: string
90+
}
91+
92+
function CheckOption(p: CheckOptionProps) {
93+
const label = () => (p.valueText ? p.valueText(p.option) : p.option)
94+
const isSelected = () => p.valueSignal.get().includes(p.option)
95+
96+
return (
97+
<ButtonIcon
98+
role="option"
99+
aria-selected={isSelected()}
100+
icon={isSelected() ? mdiCheckboxMarkedCircle : mdiCheckboxBlankCircleOutline}
101+
// iconRight={isSelected() ? mdiCheckboxMarkedCircle : mdiCheckboxBlankCircleOutline}
102+
onClick={() => toggleOption(p)}
103+
variant={p.variant ?? buttonVariant.filled}
104+
class={classMerge("justify-start text-left", p.optionClass)}
105+
>
106+
{label()}
107+
</ButtonIcon>
108+
)
109+
}
110+
111+
function toggleOption(p: CheckOptionProps) {
112+
const hasOption = p.valueSignal.get().includes(p.option)
113+
if (hasOption) {
114+
optionRemove(p)
115+
} else {
116+
optionAdd(p)
117+
}
118+
}
119+
120+
function optionRemove(p: CheckOptionProps) {
121+
const newValues = p.valueSignal.get().filter((v) => v !== p.option)
122+
p.valueSignal.set(newValues)
123+
}
124+
125+
function optionAdd(p: CheckOptionProps) {
126+
const newValues = [...p.valueSignal.get(), p.option]
127+
newValues.sort((a, b) => a.localeCompare(b))
128+
p.valueSignal.set(newValues)
129+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { demoGetTextValue } from "@/demos/input/demoGetTextValue"
2+
import { CheckMultiple } from "~ui/input/check/CheckMultiple"
3+
import { PageWrapper } from "~ui/static/page/PageWrapper"
4+
import { createSignalObject } from "~ui/utils/createSignalObject"
5+
import { arrCreate } from "~utils/arr/arrCreate"
6+
7+
const options10Strings = arrCreate<string>(10, (i) => `${i}`)
8+
const multiValueSignal = createSignalObject<string[]>([])
9+
10+
export function DemoCheckMultiple() {
11+
return (
12+
<PageWrapper>
13+
<CheckMultiple
14+
id="DemoMultiCheck2"
15+
valueSignal={multiValueSignal}
16+
getOptions={() => options10Strings}
17+
valueText={demoGetTextValue}
18+
/>
19+
</PageWrapper>
20+
)
21+
}

0 commit comments

Comments
 (0)