diff --git a/packages/desktop/src/components/assistant-message.tsx b/packages/desktop/src/components/assistant-message.tsx index 248af531..cfc9d1a4 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 (
@@ -394,6 +394,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "todowrite", render(props) { + console.log(props.input.todos) return ( ({ subtitle: `${props.input.todos?.filter((t) => t.status === "completed").length}/${props.input.todos?.length}`, }} > - -
{props.output}
+ +
+ + {(todo) => ( + +
{todo.content}
+
+ )} +
+
) diff --git a/packages/ui/src/components/checkbox.css b/packages/ui/src/components/checkbox.css new file mode 100644 index 00000000..6e1e5ddd --- /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 00000000..2009a430 --- /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> { + 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 ( + + + + + {local.icon || ( + + + + )} + + +
+ + + {resolved()} + + + + {local.description} + + +
+
+ ) +} diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 31672001..0024c9e7 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 79128181..5893ca75 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 }) => (
@@ -143,6 +146,28 @@ const Demo: Component = () => { +

Checkbox

+
+ + + + + + + + +

Icons

diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index 7d426a83..7ae6b73e 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);