Browse Source

wip(desktop): progress

Adam 2 months ago
parent
commit
0a357be160

+ 4 - 12
packages/desktop/src/DesktopInterface.tsx

@@ -2,19 +2,18 @@ import "@/index.css"
 import { Router, Route, Navigate } from "@solidjs/router"
 import { MetaProvider } from "@solidjs/meta"
 import { Font } from "@opencode-ai/ui/font"
-import { Favicon } from "@opencode-ai/ui/favicon"
 import { MarkedProvider } from "@opencode-ai/ui/context/marked"
 import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
 import { Diff } from "@opencode-ai/ui/diff"
-import { GlobalSyncProvider, useGlobalSync } from "./context/global-sync"
+import { GlobalSyncProvider } from "./context/global-sync"
 import Layout from "@/pages/layout"
+import Home from "@/pages/home"
 import DirectoryLayout from "@/pages/directory-layout"
 import Session from "@/pages/session"
 import { LayoutProvider } from "./context/layout"
 import { GlobalSDKProvider } from "./context/global-sdk"
 import { SessionProvider } from "./context/session"
-import { base64Encode } from "@opencode-ai/util/encode"
-import { createMemo, Show } from "solid-js"
+import { Show } from "solid-js"
 
 const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
 const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
@@ -35,14 +34,7 @@ export function DesktopInterface() {
               <MetaProvider>
                 <Font />
                 <Router root={Layout}>
-                  <Route
-                    path="/"
-                    component={() => {
-                      const globalSync = useGlobalSync()
-                      const slug = createMemo(() => base64Encode(globalSync.data.defaultProject!.worktree))
-                      return <Navigate href={`${slug()}/session`} />
-                    }}
-                  />
+                  <Route path="/" component={Home} />
                   <Route path="/:dir" component={DirectoryLayout}>
                     <Route path="/" component={() => <Navigate href="session" />} />
                     <Route

+ 3 - 4
packages/desktop/src/context/global-sync.tsx

@@ -51,7 +51,6 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
   init: () => {
     const [globalStore, setGlobalStore] = createStore<{
       ready: boolean
-      defaultProject?: Project // TODO: remove this when we can select projects
       projects: Project[]
       children: Record<string, State>
     }>({
@@ -165,11 +164,11 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
       sdk.client.project.list().then((x) =>
         setGlobalStore(
           "projects",
-          x.data!.filter((x) => !x.worktree.includes("opencode-test")),
+          x
+            .data!.filter((x) => !x.worktree.includes("opencode-test") && x.vcs)
+            .sort((a, b) => b.time.created - a.time.created),
         ),
       ),
-      // TODO: remove this when we can select projects
-      sdk.client.project.current().then((x) => setGlobalStore("defaultProject", x.data)),
     ]).then(() => setGlobalStore("ready", true))
 
     return {

+ 10 - 10
packages/desktop/src/context/layout.tsx

@@ -2,17 +2,15 @@ import { createStore } from "solid-js/store"
 import { createMemo } from "solid-js"
 import { createSimpleContext } from "@opencode-ai/ui/context"
 import { makePersisted } from "@solid-primitives/storage"
-import { useGlobalSync } from "./global-sync"
 
 export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
   name: "Layout",
   init: () => {
-    const globalSync = useGlobalSync()
     const [store, setStore] = makePersisted(
       createStore({
-        projects: [] as { directory: string; expanded: boolean }[],
+        projects: [] as { directory: string; expanded: boolean; lastSession?: string }[],
         sidebar: {
-          opened: true,
+          opened: false,
           width: 280,
         },
         terminal: {
@@ -24,17 +22,13 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         },
       }),
       {
-        name: "____default-layout",
+        name: "default-layout.v4",
       },
     )
 
     return {
       projects: {
-        list: createMemo(() =>
-          globalSync.data.defaultProject
-            ? [{ directory: globalSync.data.defaultProject!.worktree, expanded: true }, ...store.projects]
-            : store.projects,
-        ),
+        list: createMemo(() => store.projects),
         open(directory: string) {
           if (store.projects.find((x) => x.directory === directory)) return
           setStore("projects", (x) => [...x, { directory, expanded: true }])
@@ -48,6 +42,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         collapse(directory: string) {
           setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: false } : x)))
         },
+        lastSession(directory: string) {
+          return store.projects.find((x) => x.directory === directory)?.lastSession
+        },
+        setLastSession(directory: string, session: string | undefined) {
+          setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, lastSession: session } : x)))
+        },
       },
       sidebar: {
         opened: createMemo(() => store.sidebar.opened),

+ 54 - 12
packages/desktop/src/pages/home.tsx

@@ -1,21 +1,63 @@
 import { useGlobalSync } from "@/context/global-sync"
-import { base64Encode } from "@opencode-ai/util/encode"
-import { For } from "solid-js"
-import { A } from "@solidjs/router"
+import { For, Match, Switch } from "solid-js"
 import { Button } from "@opencode-ai/ui/button"
-import { getFilename } from "@opencode-ai/util/path"
+import { Logo } from "@opencode-ai/ui/logo"
+import { useLayout } from "@/context/layout"
+import { useNavigate } from "@solidjs/router"
+import { base64Encode } from "@opencode-ai/util/encode"
+import { Icon } from "@opencode-ai/ui/icon"
 
 export default function Home() {
+  const navigate = useNavigate()
   const sync = useGlobalSync()
+  const layout = useLayout()
+
+  function openProject(directory: string) {
+    layout.projects.open(directory)
+    navigate(`/${base64Encode(directory)}`)
+  }
+
   return (
-    <div class="flex flex-col gap-3">
-      <For each={sync.data.projects}>
-        {(project) => (
-          <Button as={A} href={base64Encode(project.worktree)}>
-            {getFilename(project.worktree)}
-          </Button>
-        )}
-      </For>
+    <div class="mx-auto mt-55">
+      <Logo class="w-xl opacity-12" />
+      <Switch>
+        <Match when={sync.data.projects.length > 0}>
+          <div class="mt-20 w-full flex flex-col gap-4">
+            <div class="flex gap-2 items-center justify-between pl-3">
+              <div class="text-14-medium text-text-strong">Recent projects</div>
+              <Button icon="folder-add-left" size="normal" class="pl-2 pr-3">
+                Open project
+              </Button>
+            </div>
+            <ol class="flex flex-col gap-2">
+              <For each={sync.data.projects.slice(0, 5)}>
+                {(project) => (
+                  <Button
+                    size="large"
+                    variant="ghost"
+                    class="text-14-mono text-left justify-between px-3"
+                    onClick={() => openProject(project.worktree)}
+                  >
+                    {project.worktree}
+                    <div class="text-14-regular text-text-weak">10m ago</div>
+                  </Button>
+                )}
+              </For>
+            </ol>
+          </div>
+        </Match>
+        <Match when={true}>
+          <div class="mt-30 mx-auto flex flex-col items-center gap-3">
+            <Icon name="folder-add-left" size="large" />
+            <div class="flex flex-col gap-1 items-center justify-center">
+              <div class="text-14-medium text-text-strong">No recent projects</div>
+              <div class="text-12-regular text-text-weak">Get started by opening a local project</div>
+            </div>
+            <div />
+            <Button class="px-3">Open project</Button>
+          </div>
+        </Match>
+      </Switch>
     </div>
   )
 }

+ 208 - 129
packages/desktop/src/pages/layout.tsx

@@ -1,10 +1,11 @@
-import { createMemo, For, ParentProps, Show } from "solid-js"
+import { createEffect, createMemo, For, Match, ParentProps, Show, Switch } from "solid-js"
 import { DateTime } from "luxon"
 import { A, useNavigate, useParams } from "@solidjs/router"
 import { useLayout } from "@/context/layout"
 import { useGlobalSync } from "@/context/global-sync"
 import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
 import { Mark } from "@opencode-ai/ui/logo"
+import { Avatar } from "@opencode-ai/ui/avatar"
 import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
 import { Button } from "@opencode-ai/ui/button"
 import { Icon } from "@opencode-ai/ui/icon"
@@ -14,6 +15,7 @@ import { Collapsible } from "@opencode-ai/ui/collapsible"
 import { DiffChanges } from "@opencode-ai/ui/diff-changes"
 import { getFilename } from "@opencode-ai/util/path"
 import { Select } from "@opencode-ai/ui/select"
+import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
 import { Session } from "@opencode-ai/sdk/v2/client"
 
 export default function Layout(props: ParentProps) {
@@ -25,15 +27,30 @@ export default function Layout(props: ParentProps) {
   const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? [])
   const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
 
+  function navigateToProject(directory: string | undefined) {
+    if (!directory) return
+    navigate(`/${base64Encode(directory)}`)
+  }
+
   function navigateToSession(session: Session | undefined) {
     if (!session) return
     navigate(`/${params.dir}/session/${session?.id}`)
   }
 
+  function closeProject(directory: string) {
+    layout.projects.close(directory)
+    navigate("/")
+  }
+
   const handleOpenProject = async () => {
     // layout.projects.open(dir.)
   }
 
+  // createEffect(() => {
+  //   if (!params.dir) return
+  //   layout.projects.setLastSession(base64Decode(params.dir), params.id)
+  // })
+
   return (
     <div class="relative h-screen flex flex-col">
       <header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex" data-tauri-drag-region>
@@ -50,61 +67,72 @@ export default function Layout(props: ParentProps) {
           <Mark class="shrink-0" />
         </A>
         <div class="pl-4 px-6 flex items-center justify-between gap-4 w-full">
-          <div class="flex items-center gap-3">
-            <div class="flex items-center gap-2">
-              <Select
-                options={layout.projects.list().map((project) => getFilename(project.directory))}
-                current={getFilename(currentDirectory())}
-                class="text-14-regular text-text-base"
-                variant="ghost"
-              />
-              <div class="text-text-weaker">/</div>
-              <Select
-                options={sessions()}
-                current={currentSession()}
-                placeholder="Select session"
-                label={(x) => x.title}
-                value={(x) => x.id}
-                onSelect={navigateToSession}
-                class="text-14-regular text-text-base max-w-md"
-                variant="ghost"
-              />
-            </div>
-            <Button as={A} href={`/${params.dir}/session`} icon="plus-small">
-              New session
-            </Button>
-          </div>
-          <div class="flex items-center gap-4">
-            <Tooltip
-              class="shrink-0"
-              value={
-                <div class="flex items-center gap-2">
-                  <span>Toggle terminal</span>
-                  <span class="text-icon-base text-12-medium">Ctrl `</span>
-                </div>
-              }
-            >
-              <Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
-                <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
-                  <Icon
-                    size="small"
-                    name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
-                    class="group-hover/terminal-toggle:hidden"
-                  />
-                  <Icon
-                    size="small"
-                    name="layout-bottom-partial"
-                    class="hidden group-hover/terminal-toggle:inline-block"
-                  />
-                  <Icon
-                    size="small"
-                    name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
-                    class="hidden group-active/terminal-toggle:inline-block"
-                  />
-                </div>
+          <Show when={params.dir && layout.projects.list().length > 0}>
+            <div class="flex items-center gap-3">
+              <div class="flex items-center gap-2">
+                <Select
+                  options={layout.projects.list().map((project) => getFilename(project.directory))}
+                  current={getFilename(currentDirectory())}
+                  onSelect={(x) => (x ? navigateToProject(x) : undefined)}
+                  class="text-14-regular text-text-base"
+                  variant="ghost"
+                >
+                  {/* @ts-ignore */}
+                  {(i) => (
+                    <div class="flex items-center gap-2">
+                      <Icon name="folder" size="small" />
+                      <div class="text-text-strong">{i}</div>
+                    </div>
+                  )}
+                </Select>
+                <div class="text-text-weaker">/</div>
+                <Select
+                  options={sessions()}
+                  current={currentSession()}
+                  placeholder="Select session"
+                  label={(x) => x.title}
+                  value={(x) => x.id}
+                  onSelect={navigateToSession}
+                  class="text-14-regular text-text-base max-w-md"
+                  variant="ghost"
+                />
+              </div>
+              <Button as={A} href={`/${params.dir}/session`} icon="plus-small">
+                New session
               </Button>
-            </Tooltip>
-          </div>
+            </div>
+            <div class="flex items-center gap-4">
+              <Tooltip
+                class="shrink-0"
+                value={
+                  <div class="flex items-center gap-2">
+                    <span>Toggle terminal</span>
+                    <span class="text-icon-base text-12-medium">Ctrl `</span>
+                  </div>
+                }
+              >
+                <Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
+                  <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
+                    <Icon
+                      size="small"
+                      name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
+                      class="group-hover/terminal-toggle:hidden"
+                    />
+                    <Icon
+                      size="small"
+                      name="layout-bottom-partial"
+                      class="hidden group-hover/terminal-toggle:inline-block"
+                    />
+                    <Icon
+                      size="small"
+                      name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
+                      class="hidden group-active/terminal-toggle:inline-block"
+                    />
+                  </div>
+                </Button>
+              </Tooltip>
+            </div>
+          </Show>
         </div>
       </header>
       <div class="h-[calc(100vh-3rem)] flex">
@@ -159,84 +187,135 @@ export default function Layout(props: ParentProps) {
                 </Show>
               </Button>
             </Tooltip>
-            <div class="flex flex-col justify-center items-start gap-4 self-stretch min-h-0">
-              <div class="hidden @[4rem]:flex size-full flex-col grow overflow-y-auto no-scrollbar">
-                <For each={layout.projects.list()}>
-                  {(project) => {
-                    const [store] = globalSync.child(project.directory)
-                    const slug = createMemo(() => base64Encode(project.directory))
-                    return (
-                      <Collapsible variant="ghost" defaultOpen class="gap-2">
-                        <Button
-                          as={"div"}
-                          variant="ghost"
-                          class="flex items-center justify-between gap-3 w-full h-8 pl-2 pr-2.25 self-stretch"
-                        >
-                          <Collapsible.Trigger class="p-0 text-left text-14-medium text-text-strong grow min-w-0 truncate">
-                            {getFilename(project.directory)}
-                          </Collapsible.Trigger>
-                          <IconButton as={A} href={`${slug()}/session`} icon="plus-small" size="normal" />
-                        </Button>
-                        <Collapsible.Content>
-                          <nav class="w-full flex flex-col gap-1.5">
-                            <For each={store.session}>
-                              {(session) => {
-                                const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
-                                return (
-                                  <A
-                                    data-active={session.id === params.id}
-                                    href={`${slug()}/session/${session.id}`}
-                                    class="group/session focus:outline-none cursor-default"
-                                  >
-                                    <Tooltip placement="right" value={session.title}>
-                                      <div
-                                        class="w-full px-2 py-1 rounded-md
+            <div class="size-full min-w-8 flex flex-col gap-2 grow min-h-0 overflow-y-auto no-scrollbar">
+              <For each={layout.projects.list()}>
+                {(project) => {
+                  const [store] = globalSync.child(project.directory)
+                  const slug = createMemo(() => base64Encode(project.directory))
+                  const name = createMemo(() => getFilename(project.directory))
+                  return (
+                    <Switch>
+                      <Match when={layout.sidebar.opened()}>
+                        <Collapsible variant="ghost" defaultOpen class="gap-2">
+                          <Button
+                            as={"div"}
+                            variant="ghost"
+                            class="group/session flex items-center justify-between gap-3 w-full px-1 self-stretch h-auto border-none"
+                          >
+                            <Collapsible.Trigger class="group/trigger flex items-center gap-3 p-0 text-left min-w-0 grow border-none">
+                              <div class="size-6 shrink-0">
+                                <Avatar
+                                  fallback={name()}
+                                  background="var(--surface-info-base)"
+                                  class="size-full group-hover/session:hidden"
+                                />
+                                <Icon
+                                  name="chevron-right"
+                                  size="large"
+                                  class="hidden size-full items-center justify-center text-text-subtle group-hover/session:flex group-data-[expanded]/trigger:rotate-90 transition-transform duration-50"
+                                />
+                              </div>
+                              <span class="truncate text-14-medium text-text-strong">{name()}</span>
+                            </Collapsible.Trigger>
+                            <div class="flex invisible gap-1 items-center group-hover/session:visible has-[[data-expanded]]:visible">
+                              <DropdownMenu>
+                                <DropdownMenu.Trigger as={IconButton} icon="dot-grid" variant="ghost" />
+                                <DropdownMenu.Portal>
+                                  <DropdownMenu.Content>
+                                    <DropdownMenu.Item onSelect={() => closeProject(project.directory)}>
+                                      <DropdownMenu.ItemLabel>Close Project</DropdownMenu.ItemLabel>
+                                    </DropdownMenu.Item>
+                                    {/* <DropdownMenu.Separator /> */}
+                                    {/* <DropdownMenu.Item> */}
+                                    {/*   <DropdownMenu.ItemLabel>Action 2</DropdownMenu.ItemLabel> */}
+                                    {/* </DropdownMenu.Item> */}
+                                  </DropdownMenu.Content>
+                                </DropdownMenu.Portal>
+                              </DropdownMenu>
+                              <Tooltip placement="bottom" value="New session">
+                                <IconButton as={A} href={`${slug()}/session`} icon="plus-small" variant="ghost" />
+                              </Tooltip>
+                            </div>
+                          </Button>
+                          <Collapsible.Content>
+                            <nav class="hidden @[4rem]:flex w-full flex-col gap-1.5">
+                              <For each={store.session}>
+                                {(session) => {
+                                  const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
+                                  return (
+                                    <A
+                                      data-active={session.id === params.id}
+                                      href={`${slug()}/session/${session.id}`}
+                                      class="group/session focus:outline-none cursor-default"
+                                    >
+                                      <Tooltip placement="right" value={session.title}>
+                                        <div
+                                          class="w-full pl-4 pr-2 py-1 rounded-md
                                                group-data-[active=true]/session:bg-surface-raised-base-hover
                                                group-hover/session:bg-surface-raised-base-hover
                                                group-focus/session:bg-surface-raised-base-hover"
-                                      >
-                                        <div class="flex items-center self-stretch gap-6 justify-between">
-                                          <span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
-                                            {session.title}
-                                          </span>
-                                          <span class="text-12-regular text-text-weak text-right whitespace-nowrap">
-                                            {Math.abs(updated().diffNow().as("seconds")) < 60
-                                              ? "Now"
-                                              : updated()
-                                                  .toRelative({ style: "short", unit: ["days", "hours", "minutes"] })
-                                                  ?.replace(" ago", "")
-                                                  ?.replace(/ days?/, "d")
-                                                  ?.replace(" min.", "m")
-                                                  ?.replace(" hr.", "h")}
-                                          </span>
-                                        </div>
-                                        <div class="hidden _flex justify-between items-center self-stretch">
-                                          <span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
-                                          <Show when={session.summary}>
-                                            {(summary) => <DiffChanges changes={summary()} />}
-                                          </Show>
+                                        >
+                                          <div class="flex items-center self-stretch gap-6 justify-between">
+                                            <span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
+                                              {session.title}
+                                            </span>
+                                            <span class="text-12-regular text-text-weak text-right whitespace-nowrap">
+                                              {Math.abs(updated().diffNow().as("seconds")) < 60
+                                                ? "Now"
+                                                : updated()
+                                                    .toRelative({
+                                                      style: "short",
+                                                      unit: ["days", "hours", "minutes"],
+                                                    })
+                                                    ?.replace(" ago", "")
+                                                    ?.replace(/ days?/, "d")
+                                                    ?.replace(" min.", "m")
+                                                    ?.replace(" hr.", "h")}
+                                            </span>
+                                          </div>
+                                          <div class="hidden _flex justify-between items-center self-stretch">
+                                            <span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
+                                            <Show when={session.summary}>
+                                              {(summary) => <DiffChanges changes={summary()} />}
+                                            </Show>
+                                          </div>
                                         </div>
-                                      </div>
-                                    </Tooltip>
-                                  </A>
-                                )
-                              }}
-                            </For>
-                          </nav>
-                          {/* <Show when={sync.session.more()}> */}
-                          {/*   <button */}
-                          {/*     class="shrink-0 self-start p-3 text-12-medium text-text-weak hover:text-text-strong" */}
-                          {/*     onClick={() => sync.session.fetch()} */}
-                          {/*   > */}
-                          {/*     Show more */}
-                          {/*   </button> */}
-                          {/* </Show> */}
-                        </Collapsible.Content>
-                      </Collapsible>
-                    )
-                  }}
-                </For>
-              </div>
+                                      </Tooltip>
+                                    </A>
+                                  )
+                                }}
+                              </For>
+                            </nav>
+                            {/* <Show when={sync.session.more()}> */}
+                            {/*   <button */}
+                            {/*     class="shrink-0 self-start p-3 text-12-medium text-text-weak hover:text-text-strong" */}
+                            {/*     onClick={() => sync.session.fetch()} */}
+                            {/*   > */}
+                            {/*     Show more */}
+                            {/*   </button> */}
+                            {/* </Show> */}
+                          </Collapsible.Content>
+                        </Collapsible>
+                      </Match>
+                      <Match when={true}>
+                        <Tooltip placement="right" value={project.directory}>
+                          <Button
+                            as={A}
+                            href={`${slug()}/session`}
+                            variant="ghost"
+                            size="large"
+                            class="flex items-center justify-center p-0 aspect-square border-none"
+                          >
+                            <div class="size-6 shrink-0 inset-0">
+                              <Avatar fallback={name()} background="var(--surface-info-base)" class="size-full" />
+                            </div>
+                          </Button>
+                        </Tooltip>
+                      </Match>
+                    </Switch>
+                  )
+                }}
+              </For>
             </div>
           </div>
           <div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">

+ 2 - 1
packages/tauri/package.json

@@ -9,7 +9,8 @@
     "dev": "vite",
     "build": "bun run typecheck && vite build",
     "preview": "vite preview",
-    "tauri": "tauri"
+    "tauri": "tauri",
+    "typecheck": "tsgo --noEmit"
   },
   "dependencies": {
     "@opencode-ai/desktop": "workspace:*",

+ 35 - 0
packages/ui/src/components/avatar.css

@@ -0,0 +1,35 @@
+[data-component="avatar"] {
+  --avatar-bg: var(--color-surface-info-base);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  border-radius: var(--radius-sm);
+  border: 1px solid var(--color-border-weak-base);
+  font-family: var(--font-mono);
+  font-weight: 500;
+  text-transform: uppercase;
+  background-color: var(--avatar-bg);
+  color: oklch(from var(--avatar-bg) calc(l * 0.72) calc(c * 8) h);
+}
+
+[data-component="avatar"][data-size="small"] {
+  width: 1.25rem;
+  height: 1.25rem;
+  font-size: 0.75rem;
+  line-height: 1;
+}
+
+[data-component="avatar"][data-size="normal"] {
+  width: 1.5rem;
+  height: 1.5rem;
+  font-size: 1.125rem;
+  line-height: 1.5rem;
+}
+
+[data-component="avatar"][data-size="large"] {
+  width: 2rem;
+  height: 2rem;
+  font-size: 1.25rem;
+  line-height: 2rem;
+}

+ 28 - 0
packages/ui/src/components/avatar.tsx

@@ -0,0 +1,28 @@
+import { type ComponentProps, splitProps, Show } from "solid-js"
+
+export interface AvatarProps extends ComponentProps<"div"> {
+  fallback: string
+  background?: string
+  size?: "small" | "normal" | "large"
+}
+
+export function Avatar(props: AvatarProps) {
+  const [split, rest] = splitProps(props, ["fallback", "background", "size", "class", "classList", "style"])
+  return (
+    <div
+      {...rest}
+      data-component="avatar"
+      data-size={split.size || "normal"}
+      classList={{
+        ...(split.classList ?? {}),
+        [split.class ?? ""]: !!split.class,
+      }}
+      style={{
+        ...(typeof split.style === "object" ? split.style : {}),
+        ...(split.background ? { "--avatar-bg": split.background } : {}),
+      }}
+    >
+      <Show when={split.fallback}>{split.fallback[0]}</Show>
+    </div>
+  )
+}

+ 119 - 0
packages/ui/src/components/dropdown-menu.css

@@ -0,0 +1,119 @@
+[data-component="dropdown-menu-content"],
+[data-component="dropdown-menu-sub-content"] {
+  min-width: 8rem;
+  overflow: hidden;
+  border-radius: var(--radius-md);
+  border: 1px solid var(--border-weak-base);
+  background-color: var(--surface-raised-stronger-non-alpha);
+  padding: 4px;
+  box-shadow: var(--shadow-md);
+  z-index: 50;
+  transform-origin: var(--kb-menu-content-transform-origin);
+
+  &[data-closed] {
+    animation: dropdown-menu-close 0.15s ease-out;
+  }
+
+  &[data-expanded] {
+    animation: dropdown-menu-open 0.15s ease-out;
+  }
+}
+
+[data-component="dropdown-menu-content"],
+[data-component="dropdown-menu-sub-content"] {
+  [data-slot="dropdown-menu-item"],
+  [data-slot="dropdown-menu-checkbox-item"],
+  [data-slot="dropdown-menu-radio-item"],
+  [data-slot="dropdown-menu-sub-trigger"] {
+    position: relative;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 4px 8px;
+    border-radius: var(--radius-sm);
+    cursor: default;
+    user-select: none;
+    outline: none;
+
+    font-family: var(--font-family-sans);
+    font-size: var(--font-size-small);
+    font-weight: var(--font-weight-medium);
+    line-height: var(--line-height-large);
+    letter-spacing: var(--letter-spacing-normal);
+    color: var(--text-strong);
+
+    &[data-highlighted] {
+      background: var(--surface-raised-base-hover);
+    }
+
+    &[data-disabled] {
+      color: var(--text-weak);
+      pointer-events: none;
+    }
+  }
+
+  [data-slot="dropdown-menu-sub-trigger"] {
+    &[data-expanded] {
+      background: var(--surface-raised-base-hover);
+    }
+  }
+
+  [data-slot="dropdown-menu-item-indicator"] {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 16px;
+    height: 16px;
+  }
+
+  [data-slot="dropdown-menu-item-label"] {
+    flex: 1;
+  }
+
+  [data-slot="dropdown-menu-item-description"] {
+    font-size: var(--font-size-x-small);
+    color: var(--text-weak);
+  }
+
+  [data-slot="dropdown-menu-separator"] {
+    height: 1px;
+    margin: 4px -4px;
+    border-top-color: var(--border-weak-base);
+  }
+
+  [data-slot="dropdown-menu-group-label"] {
+    padding: 4px 8px;
+    font-family: var(--font-family-sans);
+    font-size: var(--font-size-x-small);
+    font-weight: var(--font-weight-medium);
+    line-height: var(--line-height-large);
+    letter-spacing: var(--letter-spacing-normal);
+    color: var(--text-weak);
+  }
+
+  [data-slot="dropdown-menu-arrow"] {
+    fill: var(--surface-raised-stronger-non-alpha);
+  }
+}
+
+@keyframes dropdown-menu-open {
+  from {
+    opacity: 0;
+    transform: scale(0.96);
+  }
+  to {
+    opacity: 1;
+    transform: scale(1);
+  }
+}
+
+@keyframes dropdown-menu-close {
+  from {
+    opacity: 1;
+    transform: scale(1);
+  }
+  to {
+    opacity: 0;
+    transform: scale(0.96);
+  }
+}

+ 308 - 0
packages/ui/src/components/dropdown-menu.tsx

@@ -0,0 +1,308 @@
+import { DropdownMenu as Kobalte } from "@kobalte/core/dropdown-menu"
+import { splitProps } from "solid-js"
+import type { ComponentProps, ParentProps } from "solid-js"
+
+export interface DropdownMenuProps extends ComponentProps<typeof Kobalte> {}
+export interface DropdownMenuTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {}
+export interface DropdownMenuIconProps extends ComponentProps<typeof Kobalte.Icon> {}
+export interface DropdownMenuPortalProps extends ComponentProps<typeof Kobalte.Portal> {}
+export interface DropdownMenuContentProps extends ComponentProps<typeof Kobalte.Content> {}
+export interface DropdownMenuArrowProps extends ComponentProps<typeof Kobalte.Arrow> {}
+export interface DropdownMenuSeparatorProps extends ComponentProps<typeof Kobalte.Separator> {}
+export interface DropdownMenuGroupProps extends ComponentProps<typeof Kobalte.Group> {}
+export interface DropdownMenuGroupLabelProps extends ComponentProps<typeof Kobalte.GroupLabel> {}
+export interface DropdownMenuItemProps extends ComponentProps<typeof Kobalte.Item> {}
+export interface DropdownMenuItemLabelProps extends ComponentProps<typeof Kobalte.ItemLabel> {}
+export interface DropdownMenuItemDescriptionProps extends ComponentProps<typeof Kobalte.ItemDescription> {}
+export interface DropdownMenuItemIndicatorProps extends ComponentProps<typeof Kobalte.ItemIndicator> {}
+export interface DropdownMenuRadioGroupProps extends ComponentProps<typeof Kobalte.RadioGroup> {}
+export interface DropdownMenuRadioItemProps extends ComponentProps<typeof Kobalte.RadioItem> {}
+export interface DropdownMenuCheckboxItemProps extends ComponentProps<typeof Kobalte.CheckboxItem> {}
+export interface DropdownMenuSubProps extends ComponentProps<typeof Kobalte.Sub> {}
+export interface DropdownMenuSubTriggerProps extends ComponentProps<typeof Kobalte.SubTrigger> {}
+export interface DropdownMenuSubContentProps extends ComponentProps<typeof Kobalte.SubContent> {}
+
+function DropdownMenuRoot(props: DropdownMenuProps) {
+  return <Kobalte {...props} data-component="dropdown-menu" />
+}
+
+function DropdownMenuTrigger(props: ParentProps<DropdownMenuTriggerProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.Trigger
+      {...rest}
+      data-slot="dropdown-menu-trigger"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.Trigger>
+  )
+}
+
+function DropdownMenuIcon(props: ParentProps<DropdownMenuIconProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.Icon
+      {...rest}
+      data-slot="dropdown-menu-icon"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.Icon>
+  )
+}
+
+function DropdownMenuPortal(props: DropdownMenuPortalProps) {
+  return <Kobalte.Portal {...props} />
+}
+
+function DropdownMenuContent(props: ParentProps<DropdownMenuContentProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.Content
+      {...rest}
+      data-component="dropdown-menu-content"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.Content>
+  )
+}
+
+function DropdownMenuArrow(props: DropdownMenuArrowProps) {
+  const [local, rest] = splitProps(props, ["class", "classList"])
+  return (
+    <Kobalte.Arrow
+      {...rest}
+      data-slot="dropdown-menu-arrow"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    />
+  )
+}
+
+function DropdownMenuSeparator(props: DropdownMenuSeparatorProps) {
+  const [local, rest] = splitProps(props, ["class", "classList"])
+  return (
+    <Kobalte.Separator
+      {...rest}
+      data-slot="dropdown-menu-separator"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    />
+  )
+}
+
+function DropdownMenuGroup(props: ParentProps<DropdownMenuGroupProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.Group
+      {...rest}
+      data-slot="dropdown-menu-group"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.Group>
+  )
+}
+
+function DropdownMenuGroupLabel(props: ParentProps<DropdownMenuGroupLabelProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.GroupLabel
+      {...rest}
+      data-slot="dropdown-menu-group-label"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.GroupLabel>
+  )
+}
+
+function DropdownMenuItem(props: ParentProps<DropdownMenuItemProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.Item
+      {...rest}
+      data-slot="dropdown-menu-item"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.Item>
+  )
+}
+
+function DropdownMenuItemLabel(props: ParentProps<DropdownMenuItemLabelProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.ItemLabel
+      {...rest}
+      data-slot="dropdown-menu-item-label"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.ItemLabel>
+  )
+}
+
+function DropdownMenuItemDescription(props: ParentProps<DropdownMenuItemDescriptionProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.ItemDescription
+      {...rest}
+      data-slot="dropdown-menu-item-description"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.ItemDescription>
+  )
+}
+
+function DropdownMenuItemIndicator(props: ParentProps<DropdownMenuItemIndicatorProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.ItemIndicator
+      {...rest}
+      data-slot="dropdown-menu-item-indicator"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.ItemIndicator>
+  )
+}
+
+function DropdownMenuRadioGroup(props: ParentProps<DropdownMenuRadioGroupProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.RadioGroup
+      {...rest}
+      data-slot="dropdown-menu-radio-group"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.RadioGroup>
+  )
+}
+
+function DropdownMenuRadioItem(props: ParentProps<DropdownMenuRadioItemProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.RadioItem
+      {...rest}
+      data-slot="dropdown-menu-radio-item"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.RadioItem>
+  )
+}
+
+function DropdownMenuCheckboxItem(props: ParentProps<DropdownMenuCheckboxItemProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.CheckboxItem
+      {...rest}
+      data-slot="dropdown-menu-checkbox-item"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.CheckboxItem>
+  )
+}
+
+function DropdownMenuSub(props: DropdownMenuSubProps) {
+  return <Kobalte.Sub {...props} />
+}
+
+function DropdownMenuSubTrigger(props: ParentProps<DropdownMenuSubTriggerProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.SubTrigger
+      {...rest}
+      data-slot="dropdown-menu-sub-trigger"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.SubTrigger>
+  )
+}
+
+function DropdownMenuSubContent(props: ParentProps<DropdownMenuSubContentProps>) {
+  const [local, rest] = splitProps(props, ["class", "classList", "children"])
+  return (
+    <Kobalte.SubContent
+      {...rest}
+      data-component="dropdown-menu-sub-content"
+      classList={{
+        ...(local.classList ?? {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+    >
+      {local.children}
+    </Kobalte.SubContent>
+  )
+}
+
+export const DropdownMenu = Object.assign(DropdownMenuRoot, {
+  Trigger: DropdownMenuTrigger,
+  Icon: DropdownMenuIcon,
+  Portal: DropdownMenuPortal,
+  Content: DropdownMenuContent,
+  Arrow: DropdownMenuArrow,
+  Separator: DropdownMenuSeparator,
+  Group: DropdownMenuGroup,
+  GroupLabel: DropdownMenuGroupLabel,
+  Item: DropdownMenuItem,
+  ItemLabel: DropdownMenuItemLabel,
+  ItemDescription: DropdownMenuItemDescription,
+  ItemIndicator: DropdownMenuItemIndicator,
+  RadioGroup: DropdownMenuRadioGroup,
+  RadioItem: DropdownMenuRadioItem,
+  CheckboxItem: DropdownMenuCheckboxItem,
+  Sub: DropdownMenuSub,
+  SubTrigger: DropdownMenuSubTrigger,
+  SubContent: DropdownMenuSubContent,
+})

+ 2 - 0
packages/ui/src/components/icon.tsx

@@ -133,6 +133,7 @@ const newIcons = {
   "magnifying-glass": `<path d="M15.8332 15.8337L13.0819 13.0824M14.6143 9.39088C14.6143 12.2759 12.2755 14.6148 9.39039 14.6148C6.50532 14.6148 4.1665 12.2759 4.1665 9.39088C4.1665 6.5058 6.50532 4.16699 9.39039 4.16699C12.2755 4.16699 14.6143 6.5058 14.6143 9.39088Z" stroke="currentColor" stroke-linecap="square"/>`,
   "plus-small": `<path d="M9.99984 5.41699V10.0003M9.99984 10.0003V14.5837M9.99984 10.0003H5.4165M9.99984 10.0003H14.5832" stroke="currentColor" stroke-linecap="square"/>`,
   "chevron-down": `<path d="M6.6665 8.33325L9.99984 11.6666L13.3332 8.33325" stroke="currentColor" stroke-linecap="square"/>`,
+  "chevron-right": `<path d="M8.33301 13.3327L11.6663 9.99935L8.33301 6.66602" stroke="currentColor" stroke-linecap="square"/>`,
   "arrow-up": `<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99991 2.24121L16.0921 8.33343L15.2083 9.21731L10.6249 4.63397V17.5001H9.37492V4.63398L4.7916 9.21731L3.90771 8.33343L9.99991 2.24121Z" fill="currentColor"/>`,
   "check-small": `<path d="M6.5 11.4412L8.97059 13.5L13.5 6.5" stroke="currentColor" stroke-linecap="square"/>`,
   "edit-small-2": `<path d="M17.0834 17.0833V17.5833H17.5834V17.0833H17.0834ZM2.91675 17.0833H2.41675V17.5833H2.91675V17.0833ZM2.91675 2.91659V2.41659H2.41675V2.91659H2.91675ZM9.58341 3.41659H10.0834V2.41659H9.58341V2.91659V3.41659ZM17.5834 10.4166V9.91659H16.5834V10.4166H17.0834H17.5834ZM10.4167 7.08325L10.0632 6.7297L9.91675 6.87615V7.08325H10.4167ZM10.4167 9.58325H9.91675V10.0833H10.4167V9.58325ZM12.9167 9.58325V10.0833H13.1239L13.2703 9.93681L12.9167 9.58325ZM15.4167 2.08325L15.7703 1.7297L15.4167 1.37615L15.0632 1.7297L15.4167 2.08325ZM17.9167 4.58325L18.2703 4.93681L18.6239 4.58325L18.2703 4.2297L17.9167 4.58325ZM17.0834 17.0833V16.5833H2.91675V17.0833V17.5833H17.0834V17.0833ZM2.91675 17.0833H3.41675V2.91659H2.91675H2.41675V17.0833H2.91675ZM2.91675 2.91659V3.41659H9.58341V2.91659V2.41659H2.91675V2.91659ZM17.0834 10.4166H16.5834V17.0833H17.0834H17.5834V10.4166H17.0834ZM10.4167 7.08325H9.91675V9.58325H10.4167H10.9167V7.08325H10.4167ZM10.4167 9.58325V10.0833H12.9167V9.58325V9.08325H10.4167V9.58325ZM10.4167 7.08325L10.7703 7.43681L15.7703 2.43681L15.4167 2.08325L15.0632 1.7297L10.0632 6.7297L10.4167 7.08325ZM15.4167 2.08325L15.0632 2.43681L17.5632 4.93681L17.9167 4.58325L18.2703 4.2297L15.7703 1.7297L15.4167 2.08325ZM17.9167 4.58325L17.5632 4.2297L12.5632 9.2297L12.9167 9.58325L13.2703 9.93681L18.2703 4.93681L17.9167 4.58325Z" fill="currentColor"/>`,
@@ -170,6 +171,7 @@ const newIcons = {
   "layout-bottom": `<path d="M18.125 18.125L1.875 18.125L1.875 1.875L18.125 1.875L18.125 18.125ZM3.125 12.8308L3.125 16.875L16.875 16.875L16.875 12.8308L3.125 12.8308ZM3.125 3.125L3.125 11.5808L16.875 11.5808L16.875 3.125L3.125 3.125Z" fill="currentColor"/>`,
   "layout-bottom-partial": `<path d="M2.5 17.5L2.5 12.2059L17.5 12.2059L17.5 17.5L2.5 17.5Z" fill="currentColor" fill-opacity="40%" /><path d="M2.5 17.5L2.5 2.5M2.5 17.5L17.5 17.5M2.5 17.5L2.5 12.2059M2.5 2.5L17.5 2.5M2.5 2.5L2.5 12.2059M17.5 2.5L17.5 17.5M17.5 2.5L17.5 12.2059M17.5 17.5L17.5 12.2059M17.5 12.2059L2.5 12.2059" stroke="currentColor" stroke-linecap="square"/>`,
   "layout-bottom-full": `<path d="M2.5 17.5L2.5 12.2059L17.5 12.2059L17.5 17.5L2.5 17.5Z" fill="currentColor"/><path d="M2.5 17.5L2.5 2.5M2.5 17.5L17.5 17.5M2.5 17.5L2.5 12.2059M2.5 2.5L17.5 2.5M2.5 2.5L2.5 12.2059M17.5 2.5L17.5 17.5M17.5 2.5L17.5 12.2059M17.5 17.5L17.5 12.2059M17.5 12.2059L2.5 12.2059" stroke="currentColor" stroke-linecap="square"/>`,
+  "dot-grid": `<path d="M2.08398 9.16602H3.75065V10.8327H2.08398V9.16602Z" fill="currentColor"/><path d="M10.834 9.16602H9.16732V10.8327H10.834V9.16602Z" fill="currentColor"/><path d="M16.2507 9.16602H17.9173V10.8327H16.2507V9.16602Z" fill="currentColor"/><path d="M2.08398 9.16602H3.75065V10.8327H2.08398V9.16602Z" stroke="currentColor"/><path d="M10.834 9.16602H9.16732V10.8327H10.834V9.16602Z" stroke="currentColor"/><path d="M16.2507 9.16602H17.9173V10.8327H16.2507V9.16602Z" stroke="currentColor"/>`,
 }
 
 export interface IconProps extends ComponentProps<"svg"> {

+ 9 - 3
packages/ui/src/components/select.tsx

@@ -1,10 +1,10 @@
 import { Select as Kobalte } from "@kobalte/core/select"
-import { createMemo, splitProps, type ComponentProps } from "solid-js"
+import { createMemo, splitProps, type ComponentProps, type JSX } from "solid-js"
 import { pipe, groupBy, entries, map } from "remeda"
 import { Button, ButtonProps } from "./button"
 import { Icon } from "./icon"
 
-export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "onSelect"> & {
+export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "onSelect" | "children"> & {
   placeholder?: string
   options: T[]
   current?: T
@@ -14,6 +14,7 @@ export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "
   onSelect?: (value: T | undefined) => void
   class?: ComponentProps<"div">["class"]
   classList?: ComponentProps<"div">["classList"]
+  children?: (item: T | undefined) => JSX.Element
 }
 
 export function Select<T>(props: SelectProps<T> & ButtonProps) {
@@ -27,6 +28,7 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
     "label",
     "groupBy",
     "onSelect",
+    "children",
   ])
   const grouped = createMemo(() => {
     const result = pipe(
@@ -63,7 +65,11 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
           {...itemProps}
         >
           <Kobalte.ItemLabel data-slot="select-select-item-label">
-            {local.label ? local.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)}
+            {local.children
+              ? local.children(itemProps.item.rawValue)
+              : local.label
+                ? local.label(itemProps.item.rawValue)
+                : (itemProps.item.rawValue as string)}
           </Kobalte.ItemLabel>
           <Kobalte.ItemIndicator data-slot="select-select-item-indicator">
             <Icon name="check-small" size="small" />

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

@@ -6,6 +6,7 @@
 @import "./base.css" layer(base);
 
 @import "../components/accordion.css" layer(components);
+@import "../components/avatar.css" layer(components);
 @import "../components/basic-tool.css" layer(components);
 @import "../components/button.css" layer(components);
 @import "../components/card.css" layer(components);
@@ -14,6 +15,7 @@
 @import "../components/collapsible.css" layer(components);
 @import "../components/diff.css" layer(components);
 @import "../components/diff-changes.css" layer(components);
+@import "../components/dropdown-menu.css" layer(components);
 @import "../components/dialog.css" layer(components);
 @import "../components/file-icon.css" layer(components);
 @import "../components/icon.css" layer(components);