wip(desktop): new layout work

This commit is contained in:
Adam 2025-11-14 12:38:52 -06:00 committed by opencode
parent 5ba9b47b3c
commit 4069999b78
18 changed files with 258 additions and 141 deletions

View file

@ -0,0 +1,52 @@
import { createStore } from "solid-js/store"
import { createMemo } from "solid-js"
import { createSimpleContext } from "./helper"
import { makePersisted } from "@solid-primitives/storage"
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
name: "Layout",
init: () => {
const [store, setStore] = makePersisted(
createStore({
sidebar: {
opened: true,
width: 280,
},
review: {
state: "pane" as "pane" | "tab",
},
}),
{
name: "__default-layout",
},
)
return {
sidebar: {
opened: createMemo(() => store.sidebar.opened),
open() {
setStore("sidebar", "opened", true)
},
close() {
setStore("sidebar", "opened", false)
},
toggle() {
setStore("sidebar", "opened", (x) => !x)
},
width: createMemo(() => store.sidebar.width),
resize(width: number) {
setStore("sidebar", "width", width)
},
},
review: {
state: createMemo(() => store.review?.state ?? "closed"),
pane() {
setStore("review", "state", "pane")
},
tab() {
setStore("review", "state", "tab")
},
},
}
},
})

View file

@ -5,7 +5,6 @@ import type { FileContent, FileNode, Model, Provider, File as FileStatus } from
import { createSimpleContext } from "./helper"
import { useSDK } from "./sdk"
import { useSync } from "./sync"
import { makePersisted } from "@solid-primitives/storage"
export type LocalFile = FileNode &
Partial<{
@ -457,57 +456,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
})()
const layout = (() => {
const [store, setStore] = makePersisted(
createStore({
sidebar: {
opened: true,
width: 240,
},
review: {
state: "pane" as "pane" | "tab",
},
}),
{
name: "_default-layout",
},
)
return {
sidebar: {
opened: createMemo(() => store.sidebar.opened),
open() {
setStore("sidebar", "opened", true)
},
close() {
setStore("sidebar", "opened", false)
},
toggle() {
setStore("sidebar", "opened", (x) => !x)
},
width: createMemo(() => store.sidebar.width),
resize(width: number) {
setStore("sidebar", "width", width)
},
},
review: {
state: createMemo(() => store.review?.state ?? "closed"),
pane() {
setStore("review", "state", "pane")
},
tab() {
setStore("review", "state", "tab")
},
},
}
})()
const result = {
model,
agent,
file,
context,
layout,
}
return result
},

View file

@ -5,12 +5,15 @@ import { onCleanup } from "solid-js"
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
name: "SDK",
init: (props: { url: string }) => {
init: (props: { url: string; directory?: string }) => {
const abort = new AbortController()
const sdk = createOpencodeClient({
baseUrl: props.url,
signal: abort.signal,
})
const sdk = createOpencodeClient(
{
baseUrl: props.url,
signal: abort.signal,
},
{ directory: props.directory },
)
const emitter = createGlobalEmitter<{
[key in Event["type"]]: Extract<Event, { type: key }>
@ -27,6 +30,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
abort.abort()
})
return { client: sdk, event: emitter }
return { url: props.url, directory: props.directory, client: sdk, event: emitter }
},
})

View file

@ -3,7 +3,7 @@ import { createSimpleContext } from "./helper"
import { batch, createEffect, createMemo } from "solid-js"
import { useSync } from "./sync"
import { makePersisted } from "@solid-primitives/storage"
import { TextSelection, useLocal } from "./local"
import { TextSelection } from "./local"
import { pipe, sumBy } from "remeda"
import { AssistantMessage } from "@opencode-ai/sdk"
@ -11,7 +11,6 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
name: "Session",
init: (props: { sessionId?: string }) => {
const sync = useSync()
const local = useLocal()
const [store, setStore] = makePersisted(
createStore<{
@ -140,9 +139,6 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
setStore("tabs", "active", undefined)
return
}
if (tab.startsWith("file://")) {
await local.file.open(tab.replace("file://", ""))
}
if (tab !== "review") {
if (!store.tabs.opened.includes(tab)) {
setStore("tabs", "opened", [...store.tabs.opened, tab])

View file

@ -63,6 +63,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const sdk = useSDK()
sdk.event.listen((e) => {
// fetch the child store
// make a set store function that always rights to the child store
const event = e.details
switch (event.type) {
case "session.updated": {

View file

@ -6,10 +6,10 @@ import { MetaProvider } from "@solidjs/meta"
import { Fonts, MarkedProvider } from "@opencode-ai/ui"
import { SDKProvider } from "./context/sdk"
import { SyncProvider } from "./context/sync"
import { LocalProvider } from "./context/local"
import Layout from "@/pages/layout"
import SessionLayout from "@/pages/session-layout"
import Session from "@/pages/session"
import { LayoutProvider } from "./context/layout"
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
@ -32,7 +32,7 @@ render(
<MarkedProvider>
<SDKProvider url={url}>
<SyncProvider>
<LocalProvider>
<LayoutProvider>
<MetaProvider>
<Fonts />
<Router root={Layout}>
@ -41,7 +41,7 @@ render(
</Route>
</Router>
</MetaProvider>
</LocalProvider>
</LayoutProvider>
</SyncProvider>
</SDKProvider>
</MarkedProvider>

View file

@ -1,33 +1,85 @@
import { Button, Tooltip, DiffChanges, IconButton } from "@opencode-ai/ui"
import { createMemo, For, ParentProps, Show } from "solid-js"
import { Button, Tooltip, DiffChanges, IconButton, Mark, Icon } from "@opencode-ai/ui"
import { createMemo, For, Match, ParentProps, Show, Switch } from "solid-js"
import { DateTime } from "luxon"
import { useSync } from "@/context/sync"
import { A, useParams } from "@solidjs/router"
import { useLocal } from "@/context/local"
import { useLayout } from "@/context/layout"
export default function Layout(props: ParentProps) {
const params = useParams()
const sync = useSync()
const local = useLocal()
const layout = useLayout()
return (
<div class="relative h-screen flex flex-col">
<header class="hidden h-12 shrink-0 bg-background-strong border-b border-border-weak-base"></header>
<div class="h-[calc(100vh-0rem)] flex">
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base">
<div
classList={{
"@container w-14 pb-4 shrink-0 bg-background-weak": true,
"flex flex-col items-start self-stretch justify-between": true,
"w-12 shrink-0 px-4 py-3.5": true,
"flex items-center justify-start self-stretch": true,
"border-r border-border-weak-base": true,
"w-70": local.layout.sidebar.opened(),
}}
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
>
<div class="flex flex-col justify-center items-start gap-4 self-stretch py-2 overflow-hidden mx-auto @[4rem]:mx-0">
<div class="h-8 shrink-0 flex items-center self-stretch px-3">
<Tooltip placement="right" value="Collapse sidebar">
<IconButton icon="layout-left" variant="ghost" size="large" onClick={local.layout.sidebar.toggle} />
</Tooltip>
</div>
<Mark class="shrink-0" />
</div>
</header>
<div class="h-[calc(100vh-3rem)] flex">
<div
classList={{
"@container w-12 pb-5 shrink-0 bg-background-base": true,
"flex flex-col gap-5.5 items-start self-stretch justify-between": true,
"border-r border-border-weak-base": true,
}}
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
>
<div class="flex flex-col justify-center items-start gap-4 self-stretch p-2 overflow-hidden mx-auto @[4rem]:mx-0">
<Switch>
<Match when={layout.sidebar.opened()}>
<Button
variant="ghost"
size="large"
class="group/sidebar-toggle w-full text-left justify-start"
onClick={layout.sidebar.toggle}
>
<div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon name="layout-left" size="small" class="group-hover/sidebar-toggle:hidden" />
<Icon
name="layout-left-partial"
size="small"
class="hidden group-hover/sidebar-toggle:inline-block"
/>
<Icon
name="layout-left-full"
size="small"
class="hidden group-active/sidebar-toggle:inline-block"
/>
</div>
<div class="hidden group-hover/sidebar-toggle:block group-active/sidebar-toggle:block text-text-base">
Toggle sidebar
</div>
</Button>
</Match>
<Match when={!layout.sidebar.opened()}>
<Tooltip placement="right" value="Toggle sidebar">
<Button variant="ghost" size="large" class="group/sidebar-toggle" onClick={layout.sidebar.toggle}>
<div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon name="layout-right" size="small" class="group-hover/sidebar-toggle:hidden" />
<Icon
name="layout-right-partial"
size="small"
class="hidden group-hover/sidebar-toggle:inline-block"
/>
<Icon
name="layout-right-full"
size="small"
class="hidden group-active/sidebar-toggle:inline-block"
/>
</div>
</Button>
</Tooltip>
</Match>
</Switch>
<div class="w-full px-3">
<Button as={A} href="/session" class="hidden @[4rem]:flex w-full" size="large" icon="edit-small-2">
New Session

View file

@ -1,12 +1,24 @@
import { Show, type ParentProps } from "solid-js"
import { SessionProvider } from "@/context/session"
import { SessionProvider, useSession } from "@/context/session"
import { useParams } from "@solidjs/router"
import { SDKProvider, useSDK } from "@/context/sdk"
import { LocalProvider } from "@/context/local"
export default function Layout(props: ParentProps) {
const params = useParams()
const root = useSDK()
return (
<Show when={params.id || true} keyed>
<SessionProvider sessionId={params.id}>{props.children}</SessionProvider>
<SessionProvider sessionId={params.id}>
{(() => {
const session = useSession()
return (
<SDKProvider url={root.url} directory={session.info()?.directory}>
<LocalProvider>{props.children}</LocalProvider>
</SDKProvider>
)
})()}
</SessionProvider>
</Show>
)
}

View file

@ -13,7 +13,6 @@ import {
Code,
Tooltip,
ProgressCircle,
Button,
} from "@opencode-ai/ui"
import { FileIcon } from "@/ui"
import { MessageProgress } from "@/components/message-progress"
@ -52,8 +51,10 @@ import { Spinner } from "@/components/spinner"
import { useSession } from "@/context/session"
import { StickyAccordionHeader } from "@/components/sticky-accordion-header"
import { SessionReview } from "@/components/session-review"
import { useLayout } from "@/context/layout"
export default function Page() {
const layout = useLayout()
const local = useLocal()
const sync = useSync()
const session = useSession()
@ -176,10 +177,16 @@ export default function Page() {
setStore("activeDraggable", undefined)
}
const FileVisual = (props: { file: LocalFile }): JSX.Element => {
const FileVisual = (props: { file: LocalFile; active?: boolean }): JSX.Element => {
return (
<div class="flex items-center gap-x-1.5">
<FileIcon node={props.file} class="grayscale-100 group-data-[selected]/tab:grayscale-0" />
<FileIcon
node={props.file}
classList={{
"grayscale-100 group-data-[selected]/tab:grayscale-0": !props.active,
"grayscale-0": props.active,
}}
/>
<span
classList={{
"text-14-medium": true,
@ -300,11 +307,11 @@ export default function Page() {
</Tooltip>
</div>
</Tabs.Trigger>
<Show when={local.layout.review.state() === "tab" && session.diffs().length}>
<Show when={layout.review.state() === "tab" && session.diffs().length}>
<Tabs.Trigger
value="review"
closeButton={
<IconButton icon="collapse" size="normal" variant="ghost" onClick={local.layout.review.pane} />
<IconButton icon="collapse" size="normal" variant="ghost" onClick={layout.review.pane} />
}
>
<div class="flex items-center gap-3">
@ -343,8 +350,8 @@ export default function Page() {
<div
classList={{
"w-full flex-1 min-h-0": true,
grid: local.layout.review.state() === "tab",
flex: local.layout.review.state() === "pane",
grid: layout.review.state() === "tab",
flex: layout.review.state() === "pane",
}}
>
<div class="relative shrink-0 px-6 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-xl mx-auto">
@ -353,7 +360,7 @@ export default function Page() {
<div
classList={{
"flex-1 min-h-0 pb-20": true,
"flex items-start justify-start": local.layout.review.state() === "pane",
"flex items-start justify-start": layout.review.state() === "pane",
}}
>
<Show when={session.messages.user().length > 1}>
@ -361,8 +368,8 @@ export default function Page() {
role="list"
classList={{
"mr-8 shrink-0 flex flex-col items-start": true,
"absolute right-full w-60 mt-3 @7xl:gap-2 @7xl:mt-1": local.layout.review.state() === "tab",
"mt-3": local.layout.review.state() === "pane",
"absolute right-full w-60 mt-3 @7xl:gap-2 @7xl:mt-1": layout.review.state() === "tab",
"mt-3": layout.review.state() === "pane",
}}
>
<For each={session.messages.user()}>
@ -382,7 +389,7 @@ export default function Page() {
<li
classList={{
"group/li flex items-center self-stretch justify-end": true,
"@7xl:justify-start": local.layout.review.state() === "tab",
"@7xl:justify-start": layout.review.state() === "tab",
}}
>
<Tooltip
@ -401,7 +408,7 @@ export default function Page() {
classList={{
"group/tick flex items-center justify-start h-2 w-8 -mr-3": true,
"data-[active=true]:[&>div]:bg-icon-strong-base data-[active=true]:[&>div]:w-full": true,
"@7xl:hidden": local.layout.review.state() === "tab",
"@7xl:hidden": layout.review.state() === "tab",
}}
>
<div class="h-px w-5 bg-icon-base group-hover/tick:w-full group-hover/tick:bg-icon-strong-base" />
@ -410,7 +417,7 @@ export default function Page() {
<button
classList={{
"hidden items-center self-stretch w-full gap-x-2 cursor-default": true,
"@7xl:flex": local.layout.review.state() === "tab",
"@7xl:flex": layout.review.state() === "tab",
}}
onClick={handleClick}
>
@ -654,7 +661,7 @@ export default function Page() {
/>
</div>
</div>
<Show when={local.layout.review.state() === "pane" && session.diffs().length}>
<Show when={layout.review.state() === "pane" && session.diffs().length}>
<div
classList={{
"relative grow px-6 py-3 flex-1 min-h-0 border-l border-border-weak-base": true,
@ -665,7 +672,7 @@ export default function Page() {
</Show>
</div>
</Tabs.Content>
<Show when={local.layout.review.state() === "tab" && session.diffs().length}>
<Show when={layout.review.state() === "tab" && session.diffs().length}>
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden">
<div
classList={{
@ -718,8 +725,8 @@ export default function Page() {
},
)
return (
<div class="relative px-3 h-10 flex items-center bg-background-base border-x border-border-weak-base border-b border-b-transparent">
<Show when={file()}>{(f) => <FileVisual file={f()} />}</Show>
<div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
<Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
</div>
)
}}
@ -769,7 +776,13 @@ export default function Page() {
items={local.file.searchFiles}
key={(x) => x}
onOpenChange={(open) => setStore("fileSelectOpen", open)}
onSelect={(x) => (x ? session.layout.openTab("file://" + x) : undefined)}
onSelect={(x) => {
if (x) {
local.file.open(x)
return session.layout.openTab("file://" + x)
}
return undefined
}}
>
{(i) => (
<div

View file

@ -9,12 +9,13 @@ export type FileIconProps = JSX.GSVGAttributes<SVGSVGElement> & {
}
export const FileIcon: Component<FileIconProps> = (props) => {
const [local, rest] = splitProps(props, ["node", "class", "expanded"])
const [local, rest] = splitProps(props, ["node", "class", "classList", "expanded"])
const name = createMemo(() => chooseIconName(local.node.path, local.node.type, local.expanded || false))
return (
<svg
{...rest}
classList={{
...(local.classList ?? {}),
"shrink-0 size-4": true,
[local.class ?? ""]: !!local.class,
}}

View file

@ -5,7 +5,7 @@ import { createClient } from "./gen/client/client.gen.js"
import { type Config } from "./gen/client/types.gen.js"
import { OpencodeClient } from "./gen/sdk.gen.js"
export function createOpencodeClient(config?: Config) {
export function createOpencodeClient(config?: Config, options?: { directory?: string }) {
if (!config?.fetch) {
config = {
...config,
@ -18,5 +18,15 @@ export function createOpencodeClient(config?: Config) {
}
const client = createClient(config)
if (options?.directory) {
async function middleware(request: Request) {
const url = new URL(request.url)
url.searchParams.set("directory", options!.directory!)
return new Request(url.toString(), request)
}
client.interceptors.request.use(middleware)
}
return new OpencodeClient({ client })
}

View file

@ -19,13 +19,29 @@
border-color: var(--border-hover);
background-color: var(--surface-brand-hover);
}
&:focus:not(:disabled) {
border-color: var(--border-focus);
background-color: var(--surface-brand-focus);
}
&:active:not(:disabled) {
border-color: var(--border-active);
background-color: var(--surface-brand-active);
}
}
&[data-variant="ghost"] {
border-color: transparent;
background-color: transparent;
color: var(--text-strong);
&:hover:not(:disabled) {
background-color: var(--surface-raised-base-hover);
}
&:focus:not(:disabled) {
border-color: var(--border-focus);
background-color: var(--surface-brand-focus);
background-color: var(--surface-raised-base-hover);
}
&:active:not(:disabled) {
background-color: var(--surface-raised-base-active);
}
}
@ -38,9 +54,6 @@
&:hover:not(:disabled) {
background-color: var(--button-secondary-hover);
}
&:active:not(:disabled) {
background-color: var(--button-secondary-base);
}
&:focus:not(:disabled) {
background-color: var(--button-secondary-base);
}
@ -51,7 +64,8 @@
&:focus-visible:active {
box-shadow: none;
}
&:active {
&:active:not(:disabled) {
background-color: var(--button-secondary-base);
scale: 0.99;
transition: all 150ms ease-out;
}
@ -61,22 +75,6 @@
}
}
&[data-variant="ghost"] {
border-color: transparent;
background-color: transparent;
color: var(--text-strong);
&:hover:not(:disabled) {
background-color: var(--button-ghost-hover);
}
&:active:not(:disabled) {
background-color: var(--button-ghost-hover-2);
}
&:focus:not(:disabled) {
background-color: var(--button-ghost-hover);
}
}
&[data-size="normal"] {
height: 24px;
padding: 0 6px;

View file

@ -26,12 +26,12 @@
&:hover:not(:disabled) {
background-color: var(--icon-strong-hover);
}
&:active:not(:disabled) {
background-color: var(--icon-string-active);
}
&:focus:not(:disabled) {
background-color: var(--icon-strong-focus);
}
&:active:not(:disabled) {
background-color: var(--icon-string-active);
}
&:disabled {
background-color: var(--icon-strong-disabled);
@ -50,9 +50,6 @@
&:hover:not(:disabled) {
background-color: var(--button-secondary-hover);
}
&:active:not(:disabled) {
background-color: var(--button-secondary-base);
}
&:focus:not(:disabled) {
background-color: var(--button-secondary-base);
}
@ -63,6 +60,9 @@
&:focus-visible:active {
box-shadow: none;
}
&:active:not(:disabled) {
background-color: var(--button-secondary-base);
}
[data-slot="icon"] {
color: var(--icon-strong-base);
@ -78,26 +78,27 @@
}
&:hover:not(:disabled) {
background-color: var(--surface-base-hover);
background-color: var(--surface-raised-base-hover);
[data-slot="icon"] {
color: var(--icon-hover);
}
}
&:active:not(:disabled) {
[data-slot="icon"] {
color: var(--icon-active);
}
}
&:selected:not(:disabled) {
background-color: var(--surface-base-active);
[data-slot="icon"] {
color: var(--icon-selected);
}
/* [data-slot="icon"] { */
/* color: var(--icon-hover); */
/* } */
}
&:focus:not(:disabled) {
background-color: var(--surface-focus);
}
&:active:not(:disabled) {
background-color: var(--surface-raised-base-active);
/* [data-slot="icon"] { */
/* color: var(--icon-active); */
/* } */
}
&:selected:not(:disabled) {
background-color: var(--surface-raised-base-active);
/* [data-slot="icon"] { */
/* color: var(--icon-selected); */
/* } */
}
}
&[data-size="normal"] {

View file

@ -153,7 +153,11 @@ const newIcons = {
stop: `<rect x="5" y="5" width="10" height="10" fill="currentColor"/>`,
enter: `<path d="M5.83333 15.8334L2.5 12.5L5.83333 9.16671M3.33333 12.5H17.9167V4.58337H10" stroke="currentColor" stroke-linecap="square"/>`,
"layout-left": `<path d="M2.91675 2.91699L2.91675 2.41699L2.41675 2.41699L2.41675 2.91699L2.91675 2.91699ZM17.0834 2.91699L17.5834 2.91699L17.5834 2.41699L17.0834 2.41699L17.0834 2.91699ZM17.0834 17.0837L17.0834 17.5837L17.5834 17.5837L17.5834 17.0837L17.0834 17.0837ZM2.91675 17.0837L2.41675 17.0837L2.41675 17.5837L2.91675 17.5837L2.91675 17.0837ZM7.41674 17.0837L7.41674 17.5837L8.41674 17.5837L8.41674 17.0837L7.91674 17.0837L7.41674 17.0837ZM8.41674 2.91699L8.41674 2.41699L7.41674 2.41699L7.41674 2.91699L7.91674 2.91699L8.41674 2.91699ZM2.91675 2.91699L2.91675 3.41699L17.0834 3.41699L17.0834 2.91699L17.0834 2.41699L2.91675 2.41699L2.91675 2.91699ZM17.0834 2.91699L16.5834 2.91699L16.5834 17.0837L17.0834 17.0837L17.5834 17.0837L17.5834 2.91699L17.0834 2.91699ZM17.0834 17.0837L17.0834 16.5837L2.91675 16.5837L2.91675 17.0837L2.91675 17.5837L17.0834 17.5837L17.0834 17.0837ZM2.91675 17.0837L3.41675 17.0837L3.41675 2.91699L2.91675 2.91699L2.41675 2.91699L2.41675 17.0837L2.91675 17.0837ZM7.91674 17.0837L8.41674 17.0837L8.41674 2.91699L7.91674 2.91699L7.41674 2.91699L7.41674 17.0837L7.91674 17.0837Z" fill="currentColor"/>`,
"layout-left-partial": `<path d="M2.91732 2.91602L7.91732 2.91602L7.91732 17.0827H2.91732L2.91732 2.91602Z" fill="currentColor" fill-opacity="20%" /><path d="M2.91732 2.91602L17.084 2.91602M2.91732 2.91602L2.91732 17.0827M2.91732 2.91602L7.91732 2.91602M17.084 2.91602L17.084 17.0827M17.084 2.91602L7.91732 2.91602M17.084 17.0827L2.91732 17.0827M17.084 17.0827L7.91732 17.0827M2.91732 17.0827H7.91732M7.91732 17.0827L7.91732 2.91602" stroke="currentColor" stroke-linecap="square"/>`,
"layout-left-full": `<path d="M2.91732 2.91602L7.91732 2.91602L7.91732 17.0827H2.91732L2.91732 2.91602Z" fill="currentColor"/><path d="M2.91732 2.91602L17.084 2.91602M2.91732 2.91602L2.91732 17.0827M2.91732 2.91602L7.91732 2.91602M17.084 2.91602L17.084 17.0827M17.084 2.91602L7.91732 2.91602M17.084 17.0827L2.91732 17.0827M17.084 17.0827L7.91732 17.0827M2.91732 17.0827H7.91732M7.91732 17.0827L7.91732 2.91602" stroke="currentColor" stroke-linecap="square"/>`,
"layout-right": `<path d="M17.0832 2.91699L17.5832 2.91699L17.5832 2.41699L17.0832 2.41699L17.0832 2.91699ZM2.91651 2.91699L2.91651 2.41699L2.41651 2.41699L2.41651 2.91699L2.91651 2.91699ZM2.9165 17.0837L2.4165 17.0837L2.4165 17.5837L2.9165 17.5837L2.9165 17.0837ZM17.0832 17.0837L17.0832 17.5837L17.5832 17.5837L17.5832 17.0837L17.0832 17.0837ZM11.5832 17.0837L11.5832 17.5837L12.5832 17.5837L12.5832 17.0837L12.0832 17.0837L11.5832 17.0837ZM12.5832 2.91699L12.5832 2.41699L11.5832 2.41699L11.5832 2.91699L12.0832 2.91699L12.5832 2.91699ZM17.0832 2.91699L17.0832 2.41699L2.91651 2.41699L2.91651 2.91699L2.91651 3.41699L17.0832 3.41699L17.0832 2.91699ZM2.91651 2.91699L2.41651 2.91699L2.4165 17.0837L2.9165 17.0837L3.4165 17.0837L3.41651 2.91699L2.91651 2.91699ZM2.9165 17.0837L2.9165 17.5837L17.0832 17.5837L17.0832 17.0837L17.0832 16.5837L2.9165 16.5837L2.9165 17.0837ZM17.0832 17.0837L17.5832 17.0837L17.5832 2.91699L17.0832 2.91699L16.5832 2.91699L16.5832 17.0837L17.0832 17.0837ZM12.0832 17.0837L12.5832 17.0837L12.5832 2.91699L12.0832 2.91699L11.5832 2.91699L11.5832 17.0837L12.0832 17.0837Z" fill="currentColor"/>`,
"layout-right-partial": `<path d="M12.0827 2.91602L2.91602 2.91602L2.91602 17.0827L12.0827 17.0827L12.0827 2.91602Z" fill="currentColor" fill-opacity="20%" /><path d="M2.91602 2.91602L17.0827 2.91602L17.0827 17.0827L2.91602 17.0827M2.91602 2.91602L2.91602 17.0827M2.91602 2.91602L12.0827 2.91602L12.0827 17.0827L2.91602 17.0827" stroke="currentColor" stroke-linecap="square"/>`,
"layout-right-full": `<path d="M12.0827 2.91602L2.91602 2.91602L2.91602 17.0827L12.0827 17.0827L12.0827 2.91602Z" fill="currentColor"/><path d="M2.91602 2.91602L17.0827 2.91602L17.0827 17.0827L2.91602 17.0827M2.91602 2.91602L2.91602 17.0827M2.91602 2.91602L12.0827 2.91602L12.0827 17.0827L2.91602 17.0827" stroke="currentColor" stroke-linecap="square"/>`,
"speech-bubble": `<path d="M18.3334 10.0003C18.3334 5.57324 15.0927 2.91699 10.0001 2.91699C4.90749 2.91699 1.66675 5.57324 1.66675 10.0003C1.66675 11.1497 2.45578 13.1016 2.5771 13.3949C2.5878 13.4207 2.59839 13.4444 2.60802 13.4706C2.69194 13.6996 3.04282 14.9364 1.66675 16.7684C3.5186 17.6538 5.48526 16.1982 5.48526 16.1982C6.84592 16.9202 8.46491 17.0837 10.0001 17.0837C15.0927 17.0837 18.3334 14.4274 18.3334 10.0003Z" stroke="currentColor" stroke-linecap="square"/>`,
"align-right": `<path d="M12.292 6.04167L16.2503 9.99998L12.292 13.9583M2.91699 9.99998H15.6253M17.0837 3.75V16.25" stroke="currentColor" stroke-linecap="square"/>`,
expand: `<path d="M4.58301 10.4163V15.4163H9.58301M10.4163 4.58301H15.4163V9.58301" stroke="currentColor" stroke-linecap="square"/>`,

View file

@ -13,6 +13,7 @@ export * from "./input"
export * from "./favicon"
export * from "./fonts"
export * from "./list"
export * from "./logo"
export * from "./markdown"
export * from "./message-part"
export * from "./progress-circle"

View file

@ -0,0 +1,4 @@
[data-component="logo-mark"] {
width: 16px;
height: 20px;
}

View file

@ -0,0 +1,14 @@
export const Mark = (props: { class?: string }) => {
return (
<svg
data-component="logo-mark"
classList={{ [props.class ?? ""]: !!props.class }}
viewBox="0 0 16 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path data-slot="logo-mark-shadow" d="M12 16H4V8H12V16Z" fill="var(--icon-weak-base)" />
<path data-slot="logo-mark-o" d="M12 4H4V16H12V4ZM16 20H0V0H16V20Z" fill="var(--icon-strong-base)" />
</svg>
)
}

View file

@ -19,6 +19,7 @@
@import "../components/icon-button.css" layer(components);
@import "../components/input.css" layer(components);
@import "../components/list.css" layer(components);
@import "../components/logo.css" layer(components);
@import "../components/markdown.css" layer(components);
@import "../components/message-part.css" layer(components);
@import "../components/progress-circle.css" layer(components);