Skip to content

Commit 2940f05

Browse files
feat(check): add check boolean and check boolean single variant
1 parent 2db0171 commit 2940f05

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

lib/input/check/CheckBoolean.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { mdiCheckboxBlankCircleOutline, mdiCheckboxMarkedCircle } from "@mdi/js"
2+
import { buttonVariant } from "~ui/interactive/button/buttonCva"
3+
import { ButtonIcon } from "~ui/interactive/button/ButtonIcon"
4+
import { classMerge } from "~ui/utils/classMerge"
5+
import type { SignalObject } from "~ui/utils/createSignalObject"
6+
import type { MayHaveButtonVariant } from "~ui/utils/MayHaveButtonVariant"
7+
import type { MayHaveClass } from "~ui/utils/MayHaveClass"
8+
import type { MayHaveDisabled } from "~ui/utils/MayHaveDisabled"
9+
import type { MayHaveId } from "~ui/utils/MayHaveId"
10+
import type { MayHaveInnerClass } from "~ui/utils/MayHaveInnerClass"
11+
12+
export interface CheckBooleanProps
13+
extends MayHaveId,
14+
MayHaveButtonVariant,
15+
MayHaveClass,
16+
MayHaveInnerClass,
17+
MayHaveDisabled {
18+
valueSignal: SignalObject<boolean>
19+
valueText: (value: boolean) => string
20+
}
21+
22+
export function CheckBoolean(p: CheckBooleanProps) {
23+
const currentValue = () => p.valueSignal.get()
24+
25+
return (
26+
<div
27+
id={p.id}
28+
class={classMerge(
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 justify-center gap-1", // layout
35+
p.class,
36+
)}
37+
>
38+
<div class="flex gap-2">
39+
<ButtonIcon
40+
role="option"
41+
aria-selected={!currentValue()}
42+
icon={!currentValue() ? mdiCheckboxMarkedCircle : mdiCheckboxBlankCircleOutline}
43+
onClick={() => p.valueSignal.set(false)}
44+
variant={!currentValue() ? (p.variant ?? buttonVariant.filled) : buttonVariant.outline}
45+
class={classMerge("justify-start text-left", p.innerClass)}
46+
disabled={p.disabled}
47+
>
48+
{p.valueText(false)}
49+
</ButtonIcon>
50+
51+
<ButtonIcon
52+
role="option"
53+
aria-selected={currentValue()}
54+
icon={currentValue() ? mdiCheckboxMarkedCircle : mdiCheckboxBlankCircleOutline}
55+
onClick={() => p.valueSignal.set(true)}
56+
variant={currentValue() ? (p.variant ?? buttonVariant.filled) : buttonVariant.outline}
57+
class={classMerge("justify-start text-left", p.innerClass)}
58+
disabled={p.disabled}
59+
>
60+
{p.valueText(true)}
61+
</ButtonIcon>
62+
</div>
63+
</div>
64+
)
65+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { mdiCheckboxBlankCircleOutline, mdiCheckboxMarkedCircle } from "@mdi/js"
2+
import { buttonVariant } from "~ui/interactive/button/buttonCva"
3+
import { ButtonIcon } from "~ui/interactive/button/ButtonIcon"
4+
import { classMerge } from "~ui/utils/classMerge"
5+
import type { SignalObject } from "~ui/utils/createSignalObject"
6+
import type { MayHaveButtonVariant } from "~ui/utils/MayHaveButtonVariant"
7+
import type { MayHaveClass } from "~ui/utils/MayHaveClass"
8+
import type { MayHaveDisabled } from "~ui/utils/MayHaveDisabled"
9+
import type { MayHaveId } from "~ui/utils/MayHaveId"
10+
import type { MayHaveInnerClass } from "~ui/utils/MayHaveInnerClass"
11+
12+
export interface CheckBooleanSingleProps
13+
extends MayHaveId,
14+
MayHaveButtonVariant,
15+
MayHaveClass,
16+
MayHaveInnerClass,
17+
MayHaveDisabled {
18+
valueSignal: SignalObject<boolean>
19+
valueText: (value: boolean) => string
20+
}
21+
22+
export function CheckBooleanSingle(p: CheckBooleanSingleProps) {
23+
const label = () => p.valueText(p.valueSignal.get())
24+
const isSelected = () => p.valueSignal.get()
25+
26+
return (
27+
<div
28+
id={p.id}
29+
class={classMerge(
30+
"group border border-input",
31+
"px-2 py-2",
32+
"ring-offset-background",
33+
"rounded-md",
34+
"focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
35+
"flex flex-col justify-center", // layout
36+
p.class,
37+
)}
38+
>
39+
<ButtonIcon
40+
role="option"
41+
aria-selected={isSelected()}
42+
icon={isSelected() ? mdiCheckboxMarkedCircle : mdiCheckboxBlankCircleOutline}
43+
onClick={() => toggleOption(p)}
44+
variant={p.variant ?? buttonVariant.filled}
45+
class={classMerge("justify-start text-left", p.innerClass)}
46+
disabled={p.disabled}
47+
>
48+
{label()}
49+
</ButtonIcon>
50+
</div>
51+
)
52+
}
53+
54+
function toggleOption(p: CheckBooleanSingleProps) {
55+
p.valueSignal.set(!p.valueSignal.get())
56+
}

src/demos/demoList.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ const DemoSwitchSingle = lazy(async () => {
3131
return { default: c.DemoSwitchSingle }
3232
})
3333

34+
const DemoCheckBoolean = lazy(async () => {
35+
const c = await import("@/demos/input/DemoCheckBoolean")
36+
return { default: c.DemoCheckBoolean }
37+
})
38+
3439
const DemoInputS = lazy(async () => {
3540
const c = await import("@/demos/input/DemoInputS")
3641
return { default: c.DemoInputS }
@@ -185,6 +190,7 @@ export const demoList = {
185190
input: {
186191
DemoCheckSingle: DemoCheckSingle,
187192
DemoSwitchSingle: DemoSwitchSingle,
193+
DemoCheckBoolean: DemoCheckBoolean,
188194
DemoInputS: DemoInputS,
189195
DemoNumberInput: DemoNumberInput,
190196
DemoSelectSingleNative: DemoSelectSingleNative,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { CheckBoolean } from "~ui/input/check/CheckBoolean"
2+
import { CheckBooleanSingle } from "~ui/input/check/CheckBooleanSingle"
3+
import { PageWrapper } from "~ui/static/page/PageWrapper"
4+
import { createSignalObject } from "~ui/utils/createSignalObject"
5+
6+
const booleanValueSignal = createSignalObject<boolean>(false)
7+
const booleanBothValueSignal = createSignalObject<boolean>(false)
8+
9+
export function DemoCheckBoolean() {
10+
return (
11+
<PageWrapper>
12+
<div class="space-y-6">
13+
<CheckBooleanSingle
14+
id="DemoBooleanCheck"
15+
valueSignal={booleanValueSignal}
16+
valueText={(value) => (value ? "Enabled" : "Disabled")}
17+
class="max-w-sm"
18+
/>
19+
20+
<CheckBoolean
21+
id="DemoBooleanBothCheck"
22+
valueSignal={booleanBothValueSignal}
23+
valueText={(value) => (value ? "Enabled" : "Disabled")}
24+
class="max-w-sm"
25+
/>
26+
</div>
27+
</PageWrapper>
28+
)
29+
}

0 commit comments

Comments
 (0)