diff options
| author | Adam <[email protected]> | 2025-10-28 10:46:46 -0500 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-10-28 15:29:16 -0500 |
| commit | c1278109c9600ded4ab937686d4c7288fda2524d (patch) | |
| tree | cddddb1843564754db8f314dee9f27760ffdf153 | |
| parent | a7a88d01efc9d0d8d0c9d273c275f37d1277b43f (diff) | |
| download | opencode-c1278109c9600ded4ab937686d4c7288fda2524d.tar.gz opencode-c1278109c9600ded4ab937686d4c7288fda2524d.zip | |
wip: desktop work
| -rw-r--r-- | packages/desktop/src/components/assistant-message.tsx | 19 | ||||
| -rw-r--r-- | packages/ui/src/components/checkbox.css | 121 | ||||
| -rw-r--r-- | packages/ui/src/components/checkbox.tsx | 44 | ||||
| -rw-r--r-- | packages/ui/src/components/index.ts | 1 | ||||
| -rw-r--r-- | packages/ui/src/demo.tsx | 25 | ||||
| -rw-r--r-- | packages/ui/src/styles/index.css | 1 |
6 files changed, 206 insertions, 5 deletions
diff --git a/packages/desktop/src/components/assistant-message.tsx b/packages/desktop/src/components/assistant-message.tsx index 248af5313..cfc9d1a49 100644 --- a/packages/desktop/src/components/assistant-message.tsx +++ b/packages/desktop/src/components/assistant-message.tsx @@ -2,7 +2,7 @@ import type { Part, AssistantMessage, ReasoningPart, TextPart, ToolPart } from " import { children, Component, createMemo, For, Match, Show, Switch, type JSX } from "solid-js" import { Dynamic } from "solid-js/web" import { Markdown } from "./markdown" -import { Collapsible, Icon, IconProps } from "@opencode-ai/ui" +import { Checkbox, Collapsible, Icon, IconProps } from "@opencode-ai/ui" import { getDirectory, getFilename } from "@/utils" import type { Tool } from "opencode/tool/tool" import type { ReadTool } from "opencode/tool/read" @@ -14,11 +14,11 @@ import type { TaskTool } from "opencode/tool/task" import type { BashTool } from "opencode/tool/bash" import type { EditTool } from "opencode/tool/edit" import type { WriteTool } from "opencode/tool/write" +import type { TodoWriteTool } from "opencode/tool/todo" import { DiffChanges } from "./diff-changes" -import { TodoWriteTool } from "opencode/tool/todo" export function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) { - const filteredParts = createMemo(() => props.parts.filter((x) => x.type !== "tool" || x.tool !== "todoread")) + const filteredParts = createMemo(() => props.parts?.filter((x) => x.type !== "tool" || x.tool !== "todoread")) return ( <div class="w-full flex flex-col items-start gap-4"> <For each={filteredParts()}> @@ -394,6 +394,7 @@ ToolRegistry.register<typeof WriteTool>({ ToolRegistry.register<typeof TodoWriteTool>({ name: "todowrite", render(props) { + console.log(props.input.todos) return ( <BasicTool icon="checklist" @@ -402,8 +403,16 @@ ToolRegistry.register<typeof TodoWriteTool>({ subtitle: `${props.input.todos?.filter((t) => t.status === "completed").length}/${props.input.todos?.length}`, }} > - <Show when={false && props.output}> - <div class="whitespace-pre">{props.output}</div> + <Show when={props.input.todos?.length}> + <div class="px-12 pt-2.5 pb-6 flex flex-col gap-2"> + <For each={props.input.todos}> + {(todo) => ( + <Checkbox readOnly checked={todo.status === "completed"}> + <div classList={{ "line-through text-text-weaker": todo.status === "completed" }}>{todo.content}</div> + </Checkbox> + )} + </For> + </div> </Show> </BasicTool> ) diff --git a/packages/ui/src/components/checkbox.css b/packages/ui/src/components/checkbox.css new file mode 100644 index 000000000..6e1e5ddd5 --- /dev/null +++ b/packages/ui/src/components/checkbox.css @@ -0,0 +1,121 @@ +[data-component="checkbox"] { + display: flex; + align-items: center; + gap: 12px; + cursor: default; + + [data-slot="checkbox-input"] { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + [data-slot="checkbox-control"] { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + padding: 2px; + aspect-ratio: 1; + flex-shrink: 0; + border-radius: 4px; + border: 1px solid var(--border-weak-base); + /* background-color: var(--surface-weak); */ + } + + [data-slot="checkbox-indicator"] { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + color: var(--icon-base); + opacity: 0; + } + + /* [data-slot="checkbox-content"] { */ + /* } */ + + [data-slot="checkbox-label"] { + user-select: none; + color: var(--text-base); + + /* text-12-regular */ + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); /* 166.667% */ + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="checkbox-description"] { + color: var(--text-base); + font-family: var(--font-family-sans); + font-size: 12px; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="checkbox-error"] { + color: var(--text-error); + font-family: var(--font-family-sans); + font-size: 12px; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-normal); + } + + &:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-control"] { + border-color: var(--border-hover); + background-color: var(--surface-hover); + } + + &:focus-within:not([data-readonly]) [data-slot="checkbox-control"] { + border-color: var(--border-focus); + box-shadow: 0 0 0 2px var(--surface-focus); + } + + &[data-checked] [data-slot="checkbox-control"], + &[data-indeterminate] [data-slot="checkbox-control"] { + border-color: var(--border-base); + background-color: var(--surface-weak); + } + + &[data-checked]:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-control"], + &[data-indeterminate]:hover:not([data-disabled]) [data-slot="checkbox-control"] { + border-color: var(--border-hover); + background-color: var(--surface-hover); + } + + &[data-checked] [data-slot="checkbox-indicator"], + &[data-indeterminate] [data-slot="checkbox-indicator"] { + opacity: 1; + } + + &[data-disabled] { + cursor: not-allowed; + } + + &[data-disabled] [data-slot="checkbox-control"] { + border-color: var(--border-disabled); + background-color: var(--surface-disabled); + } + + &[data-invalid] [data-slot="checkbox-control"] { + border-color: var(--border-error); + } + + &[data-readonly] { + cursor: default; + pointer-events: none; + } +} diff --git a/packages/ui/src/components/checkbox.tsx b/packages/ui/src/components/checkbox.tsx new file mode 100644 index 000000000..2009a430b --- /dev/null +++ b/packages/ui/src/components/checkbox.tsx @@ -0,0 +1,44 @@ +import { Checkbox as Kobalte } from "@kobalte/core/checkbox" +import { children, Show, splitProps } from "solid-js" +import type { ComponentProps, JSX, ParentProps } from "solid-js" + +export interface CheckboxProps extends ParentProps<ComponentProps<typeof Kobalte>> { + hideLabel?: boolean + description?: string + icon?: JSX.Element +} + +export function Checkbox(props: CheckboxProps) { + const [local, others] = splitProps(props, ["children", "class", "label", "hideLabel", "description", "icon"]) + const resolved = children(() => local.children) + return ( + <Kobalte {...others} data-component="checkbox"> + <Kobalte.Input data-slot="checkbox-input" /> + <Kobalte.Control data-slot="checkbox-control"> + <Kobalte.Indicator data-slot="checkbox-indicator"> + {local.icon || ( + <svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path + d="M3 7.17905L5.02703 8.85135L9 3.5" + stroke="currentColor" + stroke-width="1.5" + stroke-linecap="square" + /> + </svg> + )} + </Kobalte.Indicator> + </Kobalte.Control> + <div data-slot="checkbox-content"> + <Show when={resolved()}> + <Kobalte.Label data-slot="checkbox-label" classList={{ "sr-only": local.hideLabel }}> + {resolved()} + </Kobalte.Label> + </Show> + <Show when={local.description}> + <Kobalte.Description data-slot="checkbox-description">{local.description}</Kobalte.Description> + </Show> + <Kobalte.ErrorMessage data-slot="checkbox-error" /> + </div> + </Kobalte> + ) +} diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 31672001b..0024c9e76 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -1,5 +1,6 @@ export * from "./accordion" export * from "./button" +export * from "./checkbox" export * from "./collapsible" export * from "./dialog" export * from "./icon" diff --git a/packages/ui/src/demo.tsx b/packages/ui/src/demo.tsx index 791281815..5893ca751 100644 --- a/packages/ui/src/demo.tsx +++ b/packages/ui/src/demo.tsx @@ -3,6 +3,7 @@ import { createSignal } from "solid-js" import { Accordion, Button, + Checkbox, Select, Tabs, Tooltip, @@ -21,6 +22,8 @@ const Demo: Component = () => { const [dialogOpen, setDialogOpen] = createSignal(false) const [selectDialogOpen, setSelectDialogOpen] = createSignal(false) const [inputValue, setInputValue] = createSignal("") + const [checked, setChecked] = createSignal(false) + const [termsAccepted, setTermsAccepted] = createSignal(false) const Content = (props: { dark?: boolean }) => ( <div class={`${props.dark ? "dark" : ""}`}> @@ -143,6 +146,28 @@ const Demo: Component = () => { <Input placeholder="Disabled input" disabled /> <Input type="password" placeholder="Password input" /> </section> + <h3>Checkbox</h3> + <section style={{ "flex-direction": "column", "align-items": "flex-start", gap: "12px" }}> + <Checkbox label="Simple checkbox" /> + <Checkbox label="Checked by default" defaultChecked /> + <Checkbox label="Disabled checkbox" disabled /> + <Checkbox label="Disabled & checked" disabled checked /> + <Checkbox + label="Controlled checkbox" + description="This checkbox is controlled by state" + checked={checked()} + onChange={setChecked} + /> + <Checkbox label="With description" description="This is a helpful description for the checkbox" /> + <Checkbox label="Indeterminate state" description="Useful for nested checkbox lists" indeterminate /> + <Checkbox + label="I agree to the Terms and Conditions" + description="You must agree to continue" + checked={termsAccepted()} + onChange={setTermsAccepted} + validationState={!termsAccepted() ? "invalid" : "valid"} + /> + </section> <h3>Icons</h3> <section> <Icon name="close" /> diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index 7d426a83e..7ae6b73e4 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -7,6 +7,7 @@ @import "../components/accordion.css" layer(components); @import "../components/button.css" layer(components); +@import "../components/checkbox.css" layer(components); @import "../components/collapsible.css" layer(components); @import "../components/dialog.css" layer(components); @import "../components/icon.css" layer(components); |
