Skip to content

Commit c127810

Browse files
committed
wip: desktop work
1 parent a7a88d0 commit c127810

File tree

6 files changed

+206
-5
lines changed

6 files changed

+206
-5
lines changed

packages/desktop/src/components/assistant-message.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Part, AssistantMessage, ReasoningPart, TextPart, ToolPart } from "
22
import { children, Component, createMemo, For, Match, Show, Switch, type JSX } from "solid-js"
33
import { Dynamic } from "solid-js/web"
44
import { Markdown } from "./markdown"
5-
import { Collapsible, Icon, IconProps } from "@opencode-ai/ui"
5+
import { Checkbox, Collapsible, Icon, IconProps } from "@opencode-ai/ui"
66
import { getDirectory, getFilename } from "@/utils"
77
import type { Tool } from "opencode/tool/tool"
88
import type { ReadTool } from "opencode/tool/read"
@@ -14,11 +14,11 @@ import type { TaskTool } from "opencode/tool/task"
1414
import type { BashTool } from "opencode/tool/bash"
1515
import type { EditTool } from "opencode/tool/edit"
1616
import type { WriteTool } from "opencode/tool/write"
17+
import type { TodoWriteTool } from "opencode/tool/todo"
1718
import { DiffChanges } from "./diff-changes"
18-
import { TodoWriteTool } from "opencode/tool/todo"
1919

2020
export function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
21-
const filteredParts = createMemo(() => props.parts.filter((x) => x.type !== "tool" || x.tool !== "todoread"))
21+
const filteredParts = createMemo(() => props.parts?.filter((x) => x.type !== "tool" || x.tool !== "todoread"))
2222
return (
2323
<div class="w-full flex flex-col items-start gap-4">
2424
<For each={filteredParts()}>
@@ -394,6 +394,7 @@ ToolRegistry.register<typeof WriteTool>({
394394
ToolRegistry.register<typeof TodoWriteTool>({
395395
name: "todowrite",
396396
render(props) {
397+
console.log(props.input.todos)
397398
return (
398399
<BasicTool
399400
icon="checklist"
@@ -402,8 +403,16 @@ ToolRegistry.register<typeof TodoWriteTool>({
402403
subtitle: `${props.input.todos?.filter((t) => t.status === "completed").length}/${props.input.todos?.length}`,
403404
}}
404405
>
405-
<Show when={false && props.output}>
406-
<div class="whitespace-pre">{props.output}</div>
406+
<Show when={props.input.todos?.length}>
407+
<div class="px-12 pt-2.5 pb-6 flex flex-col gap-2">
408+
<For each={props.input.todos}>
409+
{(todo) => (
410+
<Checkbox readOnly checked={todo.status === "completed"}>
411+
<div classList={{ "line-through text-text-weaker": todo.status === "completed" }}>{todo.content}</div>
412+
</Checkbox>
413+
)}
414+
</For>
415+
</div>
407416
</Show>
408417
</BasicTool>
409418
)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
[data-component="checkbox"] {
2+
display: flex;
3+
align-items: center;
4+
gap: 12px;
5+
cursor: default;
6+
7+
[data-slot="checkbox-input"] {
8+
position: absolute;
9+
width: 1px;
10+
height: 1px;
11+
padding: 0;
12+
margin: -1px;
13+
overflow: hidden;
14+
clip: rect(0, 0, 0, 0);
15+
white-space: nowrap;
16+
border-width: 0;
17+
}
18+
19+
[data-slot="checkbox-control"] {
20+
display: flex;
21+
align-items: center;
22+
justify-content: center;
23+
width: 16px;
24+
height: 16px;
25+
padding: 2px;
26+
aspect-ratio: 1;
27+
flex-shrink: 0;
28+
border-radius: 4px;
29+
border: 1px solid var(--border-weak-base);
30+
/* background-color: var(--surface-weak); */
31+
}
32+
33+
[data-slot="checkbox-indicator"] {
34+
display: flex;
35+
align-items: center;
36+
justify-content: center;
37+
width: 100%;
38+
height: 100%;
39+
color: var(--icon-base);
40+
opacity: 0;
41+
}
42+
43+
/* [data-slot="checkbox-content"] { */
44+
/* } */
45+
46+
[data-slot="checkbox-label"] {
47+
user-select: none;
48+
color: var(--text-base);
49+
50+
/* text-12-regular */
51+
font-family: var(--font-family-sans);
52+
font-size: var(--font-size-small);
53+
font-style: normal;
54+
font-weight: var(--font-weight-regular);
55+
line-height: var(--line-height-large); /* 166.667% */
56+
letter-spacing: var(--letter-spacing-normal);
57+
}
58+
59+
[data-slot="checkbox-description"] {
60+
color: var(--text-base);
61+
font-family: var(--font-family-sans);
62+
font-size: 12px;
63+
font-weight: var(--font-weight-regular);
64+
line-height: var(--line-height-normal);
65+
letter-spacing: var(--letter-spacing-normal);
66+
}
67+
68+
[data-slot="checkbox-error"] {
69+
color: var(--text-error);
70+
font-family: var(--font-family-sans);
71+
font-size: 12px;
72+
font-weight: var(--font-weight-regular);
73+
line-height: var(--line-height-normal);
74+
letter-spacing: var(--letter-spacing-normal);
75+
}
76+
77+
&:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-control"] {
78+
border-color: var(--border-hover);
79+
background-color: var(--surface-hover);
80+
}
81+
82+
&:focus-within:not([data-readonly]) [data-slot="checkbox-control"] {
83+
border-color: var(--border-focus);
84+
box-shadow: 0 0 0 2px var(--surface-focus);
85+
}
86+
87+
&[data-checked] [data-slot="checkbox-control"],
88+
&[data-indeterminate] [data-slot="checkbox-control"] {
89+
border-color: var(--border-base);
90+
background-color: var(--surface-weak);
91+
}
92+
93+
&[data-checked]:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-control"],
94+
&[data-indeterminate]:hover:not([data-disabled]) [data-slot="checkbox-control"] {
95+
border-color: var(--border-hover);
96+
background-color: var(--surface-hover);
97+
}
98+
99+
&[data-checked] [data-slot="checkbox-indicator"],
100+
&[data-indeterminate] [data-slot="checkbox-indicator"] {
101+
opacity: 1;
102+
}
103+
104+
&[data-disabled] {
105+
cursor: not-allowed;
106+
}
107+
108+
&[data-disabled] [data-slot="checkbox-control"] {
109+
border-color: var(--border-disabled);
110+
background-color: var(--surface-disabled);
111+
}
112+
113+
&[data-invalid] [data-slot="checkbox-control"] {
114+
border-color: var(--border-error);
115+
}
116+
117+
&[data-readonly] {
118+
cursor: default;
119+
pointer-events: none;
120+
}
121+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Checkbox as Kobalte } from "@kobalte/core/checkbox"
2+
import { children, Show, splitProps } from "solid-js"
3+
import type { ComponentProps, JSX, ParentProps } from "solid-js"
4+
5+
export interface CheckboxProps extends ParentProps<ComponentProps<typeof Kobalte>> {
6+
hideLabel?: boolean
7+
description?: string
8+
icon?: JSX.Element
9+
}
10+
11+
export function Checkbox(props: CheckboxProps) {
12+
const [local, others] = splitProps(props, ["children", "class", "label", "hideLabel", "description", "icon"])
13+
const resolved = children(() => local.children)
14+
return (
15+
<Kobalte {...others} data-component="checkbox">
16+
<Kobalte.Input data-slot="checkbox-input" />
17+
<Kobalte.Control data-slot="checkbox-control">
18+
<Kobalte.Indicator data-slot="checkbox-indicator">
19+
{local.icon || (
20+
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
21+
<path
22+
d="M3 7.17905L5.02703 8.85135L9 3.5"
23+
stroke="currentColor"
24+
stroke-width="1.5"
25+
stroke-linecap="square"
26+
/>
27+
</svg>
28+
)}
29+
</Kobalte.Indicator>
30+
</Kobalte.Control>
31+
<div data-slot="checkbox-content">
32+
<Show when={resolved()}>
33+
<Kobalte.Label data-slot="checkbox-label" classList={{ "sr-only": local.hideLabel }}>
34+
{resolved()}
35+
</Kobalte.Label>
36+
</Show>
37+
<Show when={local.description}>
38+
<Kobalte.Description data-slot="checkbox-description">{local.description}</Kobalte.Description>
39+
</Show>
40+
<Kobalte.ErrorMessage data-slot="checkbox-error" />
41+
</div>
42+
</Kobalte>
43+
)
44+
}

packages/ui/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from "./accordion"
22
export * from "./button"
3+
export * from "./checkbox"
34
export * from "./collapsible"
45
export * from "./dialog"
56
export * from "./icon"

packages/ui/src/demo.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createSignal } from "solid-js"
33
import {
44
Accordion,
55
Button,
6+
Checkbox,
67
Select,
78
Tabs,
89
Tooltip,
@@ -21,6 +22,8 @@ const Demo: Component = () => {
2122
const [dialogOpen, setDialogOpen] = createSignal(false)
2223
const [selectDialogOpen, setSelectDialogOpen] = createSignal(false)
2324
const [inputValue, setInputValue] = createSignal("")
25+
const [checked, setChecked] = createSignal(false)
26+
const [termsAccepted, setTermsAccepted] = createSignal(false)
2427

2528
const Content = (props: { dark?: boolean }) => (
2629
<div class={`${props.dark ? "dark" : ""}`}>
@@ -143,6 +146,28 @@ const Demo: Component = () => {
143146
<Input placeholder="Disabled input" disabled />
144147
<Input type="password" placeholder="Password input" />
145148
</section>
149+
<h3>Checkbox</h3>
150+
<section style={{ "flex-direction": "column", "align-items": "flex-start", gap: "12px" }}>
151+
<Checkbox label="Simple checkbox" />
152+
<Checkbox label="Checked by default" defaultChecked />
153+
<Checkbox label="Disabled checkbox" disabled />
154+
<Checkbox label="Disabled & checked" disabled checked />
155+
<Checkbox
156+
label="Controlled checkbox"
157+
description="This checkbox is controlled by state"
158+
checked={checked()}
159+
onChange={setChecked}
160+
/>
161+
<Checkbox label="With description" description="This is a helpful description for the checkbox" />
162+
<Checkbox label="Indeterminate state" description="Useful for nested checkbox lists" indeterminate />
163+
<Checkbox
164+
label="I agree to the Terms and Conditions"
165+
description="You must agree to continue"
166+
checked={termsAccepted()}
167+
onChange={setTermsAccepted}
168+
validationState={!termsAccepted() ? "invalid" : "valid"}
169+
/>
170+
</section>
146171
<h3>Icons</h3>
147172
<section>
148173
<Icon name="close" />

packages/ui/src/styles/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
@import "../components/accordion.css" layer(components);
99
@import "../components/button.css" layer(components);
10+
@import "../components/checkbox.css" layer(components);
1011
@import "../components/collapsible.css" layer(components);
1112
@import "../components/dialog.css" layer(components);
1213
@import "../components/icon.css" layer(components);

0 commit comments

Comments
 (0)