Browse Source

wip(desktop): progress

Adam 2 months ago
parent
commit
ada40decd1

+ 4 - 16
packages/desktop/src/components/prompt-input.tsx

@@ -3,7 +3,6 @@ import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Mat
 import { createStore } from "solid-js/store"
 import { createFocusSignal } from "@solid-primitives/active-element"
 import { useLocal } from "@/context/local"
-import { DateTime } from "luxon"
 import { ContentPart, DEFAULT_PROMPT, isPromptEqual, Prompt, useSession } from "@/context/session"
 import { useSDK } from "@/context/sdk"
 import { useNavigate } from "@solidjs/router"
@@ -14,10 +13,9 @@ import { Button } from "@opencode-ai/ui/button"
 import { Icon } from "@opencode-ai/ui/icon"
 import { Tooltip } from "@opencode-ai/ui/tooltip"
 import { IconButton } from "@opencode-ai/ui/icon-button"
-import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
 import { Select } from "@opencode-ai/ui/select"
+import { Tag } from "@opencode-ai/ui/tag"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
-import { type IconName } from "@opencode-ai/ui/icons/provider"
 
 interface PromptInputProps {
   class?: string
@@ -486,20 +484,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
               }
             >
               {(i) => (
-                <div class="w-full flex items-center justify-between gap-x-3">
-                  <div class="flex items-center gap-x-2.5 text-text-muted grow min-w-0">
-                    {/* <ProviderIcon name={i.provider.id as IconName} class="size-6 p-0.5 shrink-0" /> */}
-                    <div class="flex gap-x-3 items-baseline flex-[1_0_0]">
-                      <span class="text-14-medium text-text-strong overflow-hidden text-ellipsis">{i.name}</span>
-                      <Show when={false}>
-                        <span class="text-12-medium text-text-weak overflow-hidden text-ellipsis truncate min-w-0">
-                          {DateTime.fromFormat("unknown", "yyyy-MM-dd").toFormat("LLL yyyy")}
-                        </span>
-                      </Show>
-                    </div>
-                  </div>
+                <div class="w-full flex items-center gap-x-2.5">
+                  <span>{i.name}</span>
                   <Show when={!i.cost || i.cost?.input === 0}>
-                    <div class="overflow-hidden text-12-medium text-text-strong">Free</div>
+                    <Tag>Free</Tag>
                   </Show>
                 </div>
               )}

+ 0 - 14
packages/ui/index.html

@@ -1,14 +0,0 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1" />
-    <meta name="theme-color" content="#000000" />
-    <title>OpenCode UI</title>
-  </head>
-  <body>
-    <noscript>You need to enable JavaScript to run this app.</noscript>
-    <div id="root"></div>
-    <script src="/src/index.tsx" type="module"></script>
-  </body>
-</html>

File diff suppressed because it is too large
+ 0 - 101
packages/ui/src/components/icon.tsx


+ 14 - 3
packages/ui/src/components/select-dialog.css

@@ -103,20 +103,31 @@
       display: flex;
       flex-direction: column;
       align-items: flex-start;
-      gap: 4px;
       align-self: stretch;
 
       [data-slot="select-dialog-item"] {
         display: flex;
         width: 100%;
-        height: 32px;
-        padding: 4px 8px 4px 4px;
+        height: 28px;
+        padding: 4px 10px;
         align-items: center;
+        color: var(--text-strong);
+
+        /* text-14-medium */
+        font-family: var(--font-family-sans);
+        font-size: 14px;
+        font-style: normal;
+        font-weight: var(--font-weight-medium);
+        line-height: var(--line-height-large); /* 142.857% */
+        letter-spacing: var(--letter-spacing-normal);
 
         &[data-active="true"] {
           border-radius: var(--radius-md);
           background: var(--surface-raised-base-hover);
         }
+        &[data-selected="true"] {
+          background: var(--surface-raised-base-hover);
+        }
       }
     }
   }

+ 2 - 0
packages/ui/src/components/select-dialog.tsx

@@ -145,6 +145,7 @@ export function SelectDialog<T>(props: SelectDialogProps<T>) {
                           data-slot="select-dialog-item"
                           data-key={others.key(item)}
                           data-active={others.key(item) === active()}
+                          data-selected={item === others.current}
                           onClick={() => handleSelect(item)}
                           onMouseMove={() => {
                             setStore("mouseActive", true)
@@ -152,6 +153,7 @@ export function SelectDialog<T>(props: SelectDialogProps<T>) {
                           }}
                         >
                           {others.children(item)}
+                          <Icon name="check-small" size="small" />
                         </button>
                       )}
                     </For>

+ 37 - 0
packages/ui/src/components/tag.css

@@ -0,0 +1,37 @@
+[data-component="tag"] {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  user-select: none;
+
+  border-radius: var(--radius-xs);
+  border: 0.5px solid var(--border-weak-base);
+  background: var(--surface-raised-base);
+  color: var(--text-base);
+
+  &[data-size="normal"] {
+    height: 18px;
+    padding: 0 6px;
+
+    /* text-12-medium */
+    font-family: var(--font-family-sans);
+    font-size: var(--font-size-small);
+    font-style: normal;
+    font-weight: var(--font-weight-medium);
+    line-height: var(--line-height-large); /* 166.667% */
+    letter-spacing: var(--letter-spacing-normal);
+  }
+
+  &[data-size="large"] {
+    height: 22px;
+    padding: 0 8px;
+
+    /* text-14-medium */
+    font-family: var(--font-family-sans);
+    font-size: 14px;
+    font-style: normal;
+    font-weight: var(--font-weight-medium);
+    line-height: var(--line-height-large); /* 142.857% */
+    letter-spacing: var(--letter-spacing-normal);
+  }
+}

+ 22 - 0
packages/ui/src/components/tag.tsx

@@ -0,0 +1,22 @@
+import { type ComponentProps, splitProps } from "solid-js"
+
+export interface TagProps extends ComponentProps<"span"> {
+  size?: "normal" | "large"
+}
+
+export function Tag(props: TagProps) {
+  const [split, rest] = splitProps(props, ["size", "class", "classList", "children"])
+  return (
+    <span
+      {...rest}
+      data-component="tag"
+      data-size={split.size || "normal"}
+      classList={{
+        ...(split.classList ?? {}),
+        [split.class ?? ""]: !!split.class,
+      }}
+    >
+      {split.children}
+    </span>
+  )
+}

+ 0 - 291
packages/ui/src/demo.tsx

@@ -1,291 +0,0 @@
-import type { Component } from "solid-js"
-import { createSignal } from "solid-js"
-import "./index.css"
-import { Button } from "./components/button"
-import { Select } from "./components/select"
-import { Font } from "./components/font"
-import { Accordion } from "./components/accordion"
-import { Tabs } from "./components/tabs"
-import { Tooltip } from "./components/tooltip"
-import { Input } from "./components/input"
-import { Checkbox } from "./components/checkbox"
-import { Icon } from "./components/icon"
-import { IconButton } from "./components/icon-button"
-import { Dialog } from "./components/dialog"
-import { SelectDialog } from "./components/select-dialog"
-import { Collapsible } from "./components/collapsible"
-
-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" : ""}`}>
-      <h3>Buttons</h3>
-      <section>
-        <Button variant="primary" size="normal">
-          Normal Primary
-        </Button>
-        <Button variant="secondary" size="normal">
-          Normal Secondary
-        </Button>
-        <Button variant="ghost" size="normal">
-          Normal Ghost
-        </Button>
-        <Button variant="secondary" size="normal" disabled>
-          Normal Disabled
-        </Button>
-        <Button variant="primary" size="large">
-          Large Primary
-        </Button>
-        <Button variant="secondary" size="large">
-          Large Secondary
-        </Button>
-        <Button variant="ghost" size="large">
-          Large Ghost
-        </Button>
-        <Button variant="secondary" size="large" disabled>
-          Large Disabled
-        </Button>
-      </section>
-      <h3>Select</h3>
-      <section>
-        <Select
-          class={props.dark ? "dark" : ""}
-          variant="primary"
-          options={["Option 1", "Option 2", "Option 3"]}
-          placeholder="Select Primary"
-        />
-        <Select
-          variant="secondary"
-          class={props.dark ? "dark" : ""}
-          options={["Option 1", "Option 2", "Option 3"]}
-          placeholder="Select Secondary"
-        />
-        <Select
-          variant="ghost"
-          class={props.dark ? "dark" : ""}
-          options={["Option 1", "Option 2", "Option 3"]}
-          placeholder="Select Ghost"
-        />
-      </section>
-      <h3>Tabs</h3>
-      <section>
-        <Tabs defaultValue="tab1" style={{ width: "100%" }}>
-          <Tabs.List>
-            <Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
-            <Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
-            <Tabs.Trigger value="tab3">Tab 3</Tabs.Trigger>
-            <Tabs.Trigger value="tab4" disabled>
-              Disabled Tab
-            </Tabs.Trigger>
-          </Tabs.List>
-          <Tabs.Content value="tab1">
-            <div style={{ padding: "16px" }}>
-              <h4>Tab 1 Content</h4>
-              <p>This is the content for the first tab.</p>
-            </div>
-          </Tabs.Content>
-          <Tabs.Content value="tab2">
-            <div style={{ padding: "16px" }}>
-              <h4>Tab 2 Content</h4>
-              <p>This is the content for the second tab.</p>
-            </div>
-          </Tabs.Content>
-          <Tabs.Content value="tab3">
-            <div style={{ padding: "16px" }}>
-              <h4>Tab 3 Content</h4>
-              <p>This is the content for the third tab.</p>
-            </div>
-          </Tabs.Content>
-          <Tabs.Content value="tab4">
-            <div style={{ padding: "16px" }}>
-              <h4>Tab 4 Content</h4>
-              <p>This tab should be disabled.</p>
-            </div>
-          </Tabs.Content>
-        </Tabs>
-      </section>
-      <h3>Tooltips</h3>
-      <section>
-        <Tooltip value="This is a top tooltip" placement="top">
-          <Button variant="secondary">Top Tooltip</Button>
-        </Tooltip>
-        <Tooltip value="This is a bottom tooltip" placement="bottom">
-          <Button variant="secondary">Bottom Tooltip</Button>
-        </Tooltip>
-        <Tooltip value="This is a left tooltip" placement="left">
-          <Button variant="secondary">Left Tooltip</Button>
-        </Tooltip>
-        <Tooltip value="This is a right tooltip" placement="right">
-          <Button variant="secondary">Right Tooltip</Button>
-        </Tooltip>
-        <Tooltip value={`Dynamic tooltip: ${new Date().toLocaleTimeString()}`} placement="top">
-          <Button variant="primary">Dynamic Tooltip</Button>
-        </Tooltip>
-      </section>
-      <h3>List</h3>
-      <h3>Input</h3>
-      <section>
-        <Input
-          placeholder="Enter text..."
-          value={inputValue()}
-          onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) => setInputValue(e.currentTarget.value)}
-        />
-        <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" />
-        <Icon name="checkmark" />
-        <Icon name="chevron-down" />
-        <Icon name="chevron-up" />
-        <Icon name="chevron-left" />
-        <Icon name="chevron-right" />
-        <Icon name="search" />
-        <Icon name="loading" />
-      </section>
-      <h3>Icon Buttons</h3>
-      <section>
-        <IconButton icon="close" onClick={() => console.log("Close clicked")} />
-        <IconButton icon="checkmark" onClick={() => console.log("Check clicked")} />
-        <IconButton icon="search" onClick={() => console.log("Search clicked")} disabled />
-      </section>
-      <h3>Dialog</h3>
-      <section>
-        <Button onClick={() => setDialogOpen(true)}>Open Dialog</Button>
-        <Dialog open={dialogOpen()} onOpenChange={setDialogOpen}>
-          <Dialog.Title>Example Dialog</Dialog.Title>
-          <Dialog.Description>This is an example dialog with a title and description.</Dialog.Description>
-          <div
-            style={{
-              "margin-top": "16px",
-              display: "flex",
-              gap: "8px",
-              "justify-content": "flex-end",
-            }}
-          >
-            <Button variant="ghost" onClick={() => setDialogOpen(false)}>
-              Cancel
-            </Button>
-            <Button variant="primary" onClick={() => setDialogOpen(false)}>
-              Confirm
-            </Button>
-          </div>
-        </Dialog>
-      </section>
-      <h3>Select Dialog</h3>
-      <section>
-        <Button onClick={() => setSelectDialogOpen(true)}>Open Select Dialog</Button>
-        <SelectDialog
-          title="Select an Option"
-          defaultOpen={selectDialogOpen()}
-          onOpenChange={setSelectDialogOpen}
-          items={["Option 1", "Option 2", "Option 3", "Option 4", "Option 5"]}
-          key={(x) => x}
-          onSelect={(option) => {
-            console.log("Selected:", option)
-            setSelectDialogOpen(false)
-          }}
-          placeholder="Search options..."
-        >
-          {(item) => <div>{item}</div>}
-        </SelectDialog>
-      </section>
-      <h3>Collapsible</h3>
-      <section>
-        <Collapsible>
-          <Collapsible.Trigger>
-            <Button variant="secondary">Toggle Content</Button>
-          </Collapsible.Trigger>
-          <Collapsible.Content>
-            <div
-              style={{
-                padding: "16px",
-                "background-color": "var(--surface-base)",
-                "border-radius": "8px",
-                "margin-top": "8px",
-              }}
-            >
-              <p>This is collapsible content that can be toggled open and closed.</p>
-              <p>It animates smoothly using CSS animations.</p>
-            </div>
-          </Collapsible.Content>
-        </Collapsible>
-      </section>
-      <h3>Accordion</h3>
-      <section>
-        <Accordion collapsible>
-          <Accordion.Item value="item-1">
-            <Accordion.Header>
-              <Accordion.Trigger>What is Kobalte?</Accordion.Trigger>
-            </Accordion.Header>
-            <Accordion.Content>
-              <div style={{ padding: "16px" }}>
-                <p>Kobalte is a UI toolkit for building accessible web apps and design systems with SolidJS.</p>
-              </div>
-            </Accordion.Content>
-          </Accordion.Item>
-          <Accordion.Item value="item-2">
-            <Accordion.Header>
-              <Accordion.Trigger>Is it accessible?</Accordion.Trigger>
-            </Accordion.Header>
-            <Accordion.Content>
-              <div style={{ padding: "16px" }}>
-                <p>Yes. It adheres to the WAI-ARIA design patterns.</p>
-              </div>
-            </Accordion.Content>
-          </Accordion.Item>
-          <Accordion.Item value="item-3">
-            <Accordion.Header>
-              <Accordion.Trigger>Can it be animated?</Accordion.Trigger>
-            </Accordion.Header>
-            <Accordion.Content>
-              <div style={{ padding: "16px" }}>
-                <p>Yes! You can animate the content height using CSS animations.</p>
-              </div>
-            </Accordion.Content>
-          </Accordion.Item>
-        </Accordion>
-      </section>
-    </div>
-  )
-
-  return (
-    <>
-      <Font />
-      <main>
-        <Content />
-        <Content dark />
-      </main>
-    </>
-  )
-}
-
-export default Demo

+ 0 - 40
packages/ui/src/index.css

@@ -1,40 +0,0 @@
-@import "./styles/index.css";
-
-:root {
-  body {
-    margin: 0;
-    background-color: var(--background-base);
-    color: var(--text-base);
-  }
-  main {
-    display: flex;
-    flex-direction: row;
-    overflow-x: hidden;
-  }
-  main > div {
-    flex: 1;
-    padding: 2rem;
-    min-width: 0;
-    overflow-x: hidden;
-    display: flex;
-    flex-direction: column;
-    gap: 2rem;
-  }
-  h3 {
-    font-size: 1.25rem;
-    font-weight: 600;
-    margin: 0 0 1rem 0;
-    margin-bottom: -1rem;
-  }
-  section {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 0.75rem;
-    align-items: flex-start;
-  }
-}
-
-.dark {
-  background-color: var(--background-base);
-  color: var(--text-base);
-}

+ 0 - 22
packages/ui/src/index.tsx

@@ -1,22 +0,0 @@
-/* @refresh reload */
-import { render } from "solid-js/web"
-import { MetaProvider } from "@solidjs/meta"
-
-import Demo from "./demo"
-
-const root = document.getElementById("root")
-
-if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
-  throw new Error(
-    "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
-  )
-}
-
-render(
-  () => (
-    <MetaProvider>
-      <Demo />
-    </MetaProvider>
-  ),
-  root!,
-)

+ 1 - 0
packages/ui/src/styles/index.css

@@ -36,6 +36,7 @@
 @import "../components/session-turn.css" layer(components);
 @import "../components/sticky-accordion-header.css" layer(components);
 @import "../components/tabs.css" layer(components);
+@import "../components/tag.css" layer(components);
 @import "../components/tooltip.css" layer(components);
 @import "../components/typewriter.css" layer(components);
 

+ 1 - 0
packages/ui/src/styles/theme.css

@@ -40,6 +40,7 @@
   --container-6xl: 72rem;
   --container-7xl: 80rem;
 
+  --radius-xs: 0.125rem;
   --radius-sm: 0.25rem;
   --radius-md: 0.375rem;
   --radius-lg: 0.5rem;

Some files were not shown because too many files changed in this diff