summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAaron Iker <[email protected]>2026-01-30 18:57:49 +0100
committerGitHub <[email protected]>2026-01-30 17:57:49 +0000
commit20619a6a26ec0cfc2707b7ed13387715e9f9cdaa (patch)
tree50b8aa69da65642789c3fa92f65034a9dd1361df
parent1bbe84ed8d0eceb96af06b971690ebd0b0213580 (diff)
downloadopencode-20619a6a26ec0cfc2707b7ed13387715e9f9cdaa.tar.gz
opencode-20619a6a26ec0cfc2707b7ed13387715e9f9cdaa.zip
feat: Transitions, spacing, scroll fade, prompt area update (#11168)
Co-authored-by: Github Action <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: aaroniker <[email protected]>
-rw-r--r--packages/app/src/components/dialog-select-model.tsx7
-rw-r--r--packages/app/src/components/prompt-input.tsx148
-rw-r--r--packages/app/src/components/session-context-usage.tsx6
-rw-r--r--packages/app/src/components/settings-general.tsx42
-rw-r--r--packages/app/src/components/settings-keybinds.tsx10
-rw-r--r--packages/desktop/src-tauri/src/lib.rs1
-rw-r--r--packages/ui/src/components/accordion.css179
-rw-r--r--packages/ui/src/components/accordion.tsx43
-rw-r--r--packages/ui/src/components/avatar.css64
-rw-r--r--packages/ui/src/components/basic-tool.css168
-rw-r--r--packages/ui/src/components/button.css336
-rw-r--r--packages/ui/src/components/button.tsx2
-rw-r--r--packages/ui/src/components/card.css50
-rw-r--r--packages/ui/src/components/checkbox.css253
-rw-r--r--packages/ui/src/components/code.css4
-rw-r--r--packages/ui/src/components/collapsible.css174
-rw-r--r--packages/ui/src/components/collapsible.tsx42
-rw-r--r--packages/ui/src/components/cycle-label.css52
-rw-r--r--packages/ui/src/components/cycle-label.tsx132
-rw-r--r--packages/ui/src/components/dialog.css337
-rw-r--r--packages/ui/src/components/diff-changes.css66
-rw-r--r--packages/ui/src/components/diff.css60
-rw-r--r--packages/ui/src/components/dropdown-menu.css239
-rw-r--r--packages/ui/src/components/file-icon.css6
-rw-r--r--packages/ui/src/components/hover-card.css102
-rw-r--r--packages/ui/src/components/icon-button.css279
-rw-r--r--packages/ui/src/components/icon.css54
-rw-r--r--packages/ui/src/components/icon.tsx4
-rw-r--r--packages/ui/src/components/image-preview.css110
-rw-r--r--packages/ui/src/components/inline-input.css28
-rw-r--r--packages/ui/src/components/keybind.css30
-rw-r--r--packages/ui/src/components/line-comment.css140
-rw-r--r--packages/ui/src/components/list.css652
-rw-r--r--packages/ui/src/components/list.tsx11
-rw-r--r--packages/ui/src/components/logo.css4
-rw-r--r--packages/ui/src/components/markdown.css416
-rw-r--r--packages/ui/src/components/message-nav.css179
-rw-r--r--packages/ui/src/components/message-part.css1482
-rw-r--r--packages/ui/src/components/message-part.tsx3
-rw-r--r--packages/ui/src/components/morph-chevron.css10
-rw-r--r--packages/ui/src/components/morph-chevron.tsx73
-rw-r--r--packages/ui/src/components/popover.css216
-rw-r--r--packages/ui/src/components/progress-circle.css16
-rw-r--r--packages/ui/src/components/progress-circle.tsx46
-rw-r--r--packages/ui/src/components/provider-icon.css6
-rw-r--r--packages/ui/src/components/radio-group.css322
-rw-r--r--packages/ui/src/components/reasoning-icon.css10
-rw-r--r--packages/ui/src/components/reasoning-icon.tsx32
-rw-r--r--packages/ui/src/components/resize-handle.css114
-rw-r--r--packages/ui/src/components/scroll-fade.css122
-rw-r--r--packages/ui/src/components/scroll-fade.tsx207
-rw-r--r--packages/ui/src/components/scroll-reveal.tsx141
-rw-r--r--packages/ui/src/components/select.css349
-rw-r--r--packages/ui/src/components/select.tsx18
-rw-r--r--packages/ui/src/components/session-review.css429
-rw-r--r--packages/ui/src/components/session-review.tsx5
-rw-r--r--packages/ui/src/components/session-turn.css1148
-rw-r--r--packages/ui/src/components/session-turn.tsx2
-rw-r--r--packages/ui/src/components/spinner.css8
-rw-r--r--packages/ui/src/components/sticky-accordion-header.css24
-rw-r--r--packages/ui/src/components/switch.css261
-rw-r--r--packages/ui/src/components/tabs.css901
-rw-r--r--packages/ui/src/components/tag.css63
-rw-r--r--packages/ui/src/components/text-field.css264
-rw-r--r--packages/ui/src/components/toast.css382
-rw-r--r--packages/ui/src/components/tooltip.css116
-rw-r--r--packages/ui/src/components/typewriter.css18
-rw-r--r--packages/ui/src/context/dialog.tsx24
-rw-r--r--packages/ui/src/styles/index.css2
-rw-r--r--packages/ui/src/styles/utilities.css235
70 files changed, 6254 insertions, 5225 deletions
diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx
index 4f0dcc3ee..4d96c6c5f 100644
--- a/packages/app/src/components/dialog-select-model.tsx
+++ b/packages/app/src/components/dialog-select-model.tsx
@@ -90,7 +90,7 @@ const ModelList: Component<{
export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
provider?: string
- children?: JSX.Element
+ children?: JSX.Element | ((open: boolean) => JSX.Element)
triggerAs?: T
triggerProps?: ComponentProps<T>
}) {
@@ -182,12 +182,13 @@ export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
as={props.triggerAs ?? "div"}
{...(props.triggerProps as any)}
>
- {props.children}
+ {typeof props.children === "function" ? props.children(store.open) : props.children}
</Kobalte.Trigger>
<Kobalte.Portal>
<Kobalte.Content
+ class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden"
+ data-component="model-popover-content"
ref={(el) => setStore("content", el)}
- class="w-72 h-80 flex flex-col p-2 rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden"
onEscapeKeyDown={(event) => {
setStore("dismiss", "escape")
setStore("open", false)
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index 5c1d417eb..313f0402d 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -32,7 +32,9 @@ import { useNavigate, useParams } from "@solidjs/router"
import { useSync } from "@/context/sync"
import { useComments } from "@/context/comments"
import { FileIcon } from "@opencode-ai/ui/file-icon"
+import { MorphChevron } from "@opencode-ai/ui/morph-chevron"
import { Button } from "@opencode-ai/ui/button"
+import { CycleLabel } from "@opencode-ai/ui/cycle-label"
import { Icon } from "@opencode-ai/ui/icon"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import type { IconName } from "@opencode-ai/ui/icons/provider"
@@ -42,6 +44,7 @@ import { Select } from "@opencode-ai/ui/select"
import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/util/path"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { ImagePreview } from "@opencode-ai/ui/image-preview"
+import { ReasoningIcon } from "@opencode-ai/ui/reasoning-icon"
import { ModelSelectorPopover } from "@/components/dialog-select-model"
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
import { useProviders } from "@/hooks/use-providers"
@@ -922,7 +925,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
.abort({
sessionID,
})
- .catch(() => {})
+ .catch(() => { })
}
const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
@@ -1252,7 +1255,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
clearInput()
client.session
.shell({
- sessionID: session.id,
+ sessionID: session?.id || "",
agent,
model,
command: text,
@@ -1275,7 +1278,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
clearInput()
client.session
.command({
- sessionID: session.id,
+ sessionID: session?.id || "",
command: commandName,
arguments: args.join(" "),
agent,
@@ -1348,18 +1351,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const contextParts: Array<
| {
- id: string
- type: "text"
- text: string
- synthetic?: boolean
- }
+ id: string
+ type: "text"
+ text: string
+ synthetic?: boolean
+ }
| {
- id: string
- type: "file"
- mime: string
- url: string
- filename?: string
- }
+ id: string
+ type: "file"
+ mime: string
+ url: string
+ filename?: string
+ }
> = []
const commentNote = (path: string, selection: FileSelection | undefined, comment: string) => {
@@ -1431,13 +1434,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const optimisticParts = requestParts.map((part) => ({
...part,
- sessionID: session.id,
+ sessionID: session?.id || "",
messageID,
})) as unknown as Part[]
const optimisticMessage: Message = {
id: messageID,
- sessionID: session.id,
+ sessionID: session?.id || "",
role: "user",
time: { created: Date.now() },
agent,
@@ -1448,9 +1451,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (sessionDirectory === projectDirectory) {
sync.set(
produce((draft) => {
- const messages = draft.message[session.id]
+ const messages = draft.message[session?.id || ""]
if (!messages) {
- draft.message[session.id] = [optimisticMessage]
+ draft.message[session?.id || ""] = [optimisticMessage]
} else {
const result = Binary.search(messages, messageID, (m) => m.id)
messages.splice(result.index, 0, optimisticMessage)
@@ -1466,9 +1469,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
globalSync.child(sessionDirectory)[1](
produce((draft) => {
- const messages = draft.message[session.id]
+ const messages = draft.message[session?.id || ""]
if (!messages) {
- draft.message[session.id] = [optimisticMessage]
+ draft.message[session?.id || ""] = [optimisticMessage]
} else {
const result = Binary.search(messages, messageID, (m) => m.id)
messages.splice(result.index, 0, optimisticMessage)
@@ -1485,7 +1488,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (sessionDirectory === projectDirectory) {
sync.set(
produce((draft) => {
- const messages = draft.message[session.id]
+ const messages = draft.message[session?.id || ""]
if (messages) {
const result = Binary.search(messages, messageID, (m) => m.id)
if (result.found) messages.splice(result.index, 1)
@@ -1498,7 +1501,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
globalSync.child(sessionDirectory)[1](
produce((draft) => {
- const messages = draft.message[session.id]
+ const messages = draft.message[session?.id || ""]
if (messages) {
const result = Binary.search(messages, messageID, (m) => m.id)
if (result.found) messages.splice(result.index, 1)
@@ -1519,15 +1522,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const worktree = WorktreeState.get(sessionDirectory)
if (!worktree || worktree.status !== "pending") return true
- if (sessionDirectory === projectDirectory) {
- sync.set("session_status", session.id, { type: "busy" })
+ if (sessionDirectory === projectDirectory && session?.id) {
+ sync.set("session_status", session?.id, { type: "busy" })
}
const controller = new AbortController()
const cleanup = () => {
- if (sessionDirectory === projectDirectory) {
- sync.set("session_status", session.id, { type: "idle" })
+ if (sessionDirectory === projectDirectory && session?.id) {
+ sync.set("session_status", session?.id, { type: "idle" })
}
removeOptimisticMessage()
for (const item of commentItems) {
@@ -1544,7 +1547,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
restoreInput()
}
- pending.set(session.id, { abort: controller, cleanup })
+ pending.set(session?.id || "", { abort: controller, cleanup })
const abort = new Promise<Awaited<ReturnType<typeof WorktreeState.wait>>>((resolve) => {
if (controller.signal.aborted) {
@@ -1572,7 +1575,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (timer.id === undefined) return
clearTimeout(timer.id)
})
- pending.delete(session.id)
+ pending.delete(session?.id || "")
if (controller.signal.aborted) return false
if (result.status === "failed") throw new Error(result.message)
return true
@@ -1582,7 +1585,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const ok = await waitForWorktree()
if (!ok) return
await client.session.prompt({
- sessionID: session.id,
+ sessionID: session?.id || "",
agent,
model,
messageID,
@@ -1592,9 +1595,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
void send().catch((err) => {
- pending.delete(session.id)
- if (sessionDirectory === projectDirectory) {
- sync.set("session_status", session.id, { type: "idle" })
+ pending.delete(session?.id || "")
+ if (sessionDirectory === projectDirectory && session?.id) {
+ sync.set("session_status", session?.id, { type: "idle" })
}
showToast({
title: language.t("prompt.toast.promptSendFailed.title"),
@@ -1616,6 +1619,28 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
}
+ const currrentModelVariant = createMemo(() => {
+ const modelVariant = local.model.variant.current() ?? ""
+ return modelVariant === "xhigh"
+ ? "xHigh"
+ : modelVariant.length > 0
+ ? modelVariant[0].toUpperCase() + modelVariant.slice(1)
+ : "Default"
+ })
+
+ const reasoningPercentage = createMemo(() => {
+ const variants = local.model.variant.list()
+ const current = local.model.variant.current()
+ const totalEntries = variants.length + 1
+
+ if (totalEntries <= 2 || current === "Default") {
+ return 0
+ }
+
+ const currentIndex = current ? variants.indexOf(current) + 1 : 0
+ return ((currentIndex + 1) / totalEntries) * 100
+ }, [local.model.variant])
+
return (
<div class="relative size-full _max-h-[320px] flex flex-col gap-3">
<Show when={store.popover}>
@@ -1668,7 +1693,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</>
}
>
- <Icon name="brain" size="small" class="text-icon-info-active shrink-0" />
+ <Icon name="brain" size="normal" class="text-icon-info-active shrink-0" />
<span class="text-14-regular text-text-strong whitespace-nowrap">
@{(item as { type: "agent"; name: string }).name}
</span>
@@ -1729,9 +1754,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}}
>
<Show when={store.dragging}>
- <div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 pointer-events-none">
+ <div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 mr-1 pointer-events-none">
<div class="flex flex-col items-center gap-2 text-text-weak">
- <Icon name="photo" class="size-8" />
+ <Icon name="photo" size={18} class="text-icon-base stroke-1.5" />
<span class="text-14-regular">{language.t("prompt.dropzone.label")}</span>
</div>
</div>
@@ -1770,7 +1795,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}}
>
<div class="flex items-center gap-1.5">
- <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" />
+ <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-7" />
<div class="flex items-center text-11-regular min-w-0 font-medium">
<span class="text-text-strong whitespace-nowrap">{getFilenameTruncated(item.path, 14)}</span>
<Show when={item.selection}>
@@ -1787,7 +1812,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
type="button"
icon="close-small"
variant="ghost"
- class="ml-auto h-5 w-5 opacity-0 group-hover:opacity-100 transition-all"
+ class="ml-auto size-7 opacity-0 group-hover:opacity-100 transition-all"
onClick={(e) => {
e.stopPropagation()
if (item.commentID) comments.remove(item.path, item.commentID)
@@ -1817,7 +1842,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
when={attachment.mime.startsWith("image/")}
fallback={
<div class="size-16 rounded-md bg-surface-base flex items-center justify-center border border-border-base">
- <Icon name="folder" class="size-6 text-text-weak" />
+ <Icon name="folder" size="normal" class="size-6 text-text-base" />
</div>
}
>
@@ -1891,7 +1916,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</Show>
</div>
<div class="relative p-3 flex items-center justify-between">
- <div class="flex items-center justify-start gap-0.5">
+ <div class="flex items-center justify-start gap-2">
<Switch>
<Match when={store.mode === "shell"}>
<div class="flex items-center gap-2 px-2 h-6">
@@ -1922,12 +1947,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
title={language.t("command.model.choose")}
keybind={command.keybind("model.choose")}
>
- <Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
+ <Button
+ as="div"
+ variant="ghost"
+ class="px-2"
+ onClick={() => dialog.render(<DialogSelectModelUnpaid />, "select-model")}
+ >
<Show when={local.model.current()?.provider?.id}>
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
</Show>
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
- <Icon name="chevron-down" size="small" />
+ <MorphChevron expanded={dialog.isActive("select-model")} />
</Button>
</TooltipKeybind>
}
@@ -1938,11 +1968,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
keybind={command.keybind("model.choose")}
>
<ModelSelectorPopover triggerAs={Button} triggerProps={{ variant: "ghost" }}>
- <Show when={local.model.current()?.provider?.id}>
- <ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
- </Show>
- {local.model.current()?.name ?? language.t("dialog.model.select.title")}
- <Icon name="chevron-down" size="small" />
+ {(open) => (
+ <>
+ <Show when={local.model.current()?.provider?.id}>
+ <ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
+ </Show>
+ {local.model.current()?.name ?? language.t("dialog.model.select.title")}
+ <MorphChevron expanded={open} class="text-text-weak" />
+ </>
+ )}
</ModelSelectorPopover>
</TooltipKeybind>
</Show>
@@ -1955,10 +1989,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Button
data-action="model-variant-cycle"
variant="ghost"
- class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
+ class="text-text-strong text-12-regular"
onClick={() => local.model.variant.cycle()}
>
- {local.model.variant.current() ?? language.t("common.default")}
+ <Show when={local.model.variant.list().length > 1}>
+ <ReasoningIcon percentage={reasoningPercentage()} size={16} strokeWidth={1.25} />
+ </Show>
+ <CycleLabel value={currrentModelVariant()} />
</Button>
</TooltipKeybind>
</Show>
@@ -1972,7 +2009,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
variant="ghost"
onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)}
classList={{
- "_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true,
+ "_hidden group-hover/prompt-input:flex items-center justify-center": true,
"text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory),
"hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory),
}}
@@ -1994,7 +2031,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</Match>
</Switch>
</div>
- <div class="flex items-center gap-3 absolute right-3 bottom-3">
+ <div class="flex items-center gap-1 absolute right-3 bottom-3">
<input
ref={fileInputRef}
type="file"
@@ -2006,18 +2043,19 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
e.currentTarget.value = ""
}}
/>
- <div class="flex items-center gap-2">
+ <div class="flex items-center gap-1.5 mr-1.5">
<SessionContextUsage />
<Show when={store.mode === "normal"}>
<Tooltip placement="top" value={language.t("prompt.action.attachFile")}>
<Button
type="button"
variant="ghost"
- class="size-6"
+ size="small"
+ class="px-1"
onClick={() => fileInputRef.click()}
aria-label={language.t("prompt.action.attachFile")}
>
- <Icon name="photo" class="size-4.5" />
+ <Icon name="photo" class="size-6 text-icon-base" />
</Button>
</Tooltip>
</Show>
@@ -2036,7 +2074,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Match when={true}>
<div class="flex items-center gap-2">
<span>{language.t("prompt.action.send")}</span>
- <Icon name="enter" size="small" class="text-icon-base" />
+ <Icon name="enter" size="normal" class="text-icon-base" />
</div>
</Match>
</Switch>
@@ -2047,7 +2085,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
disabled={!prompt.dirty() && !working()}
icon={working() ? "stop" : "arrow-up"}
variant="primary"
- class="h-6 w-4.5"
+ class="h-6 w-5.5"
aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")}
/>
</Tooltip>
diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx
index 1e37d8f6a..92b060212 100644
--- a/packages/app/src/components/session-context-usage.tsx
+++ b/packages/app/src/components/session-context-usage.tsx
@@ -64,8 +64,8 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
}
const circle = () => (
- <div class="p-1">
- <ProgressCircle size={16} strokeWidth={2} percentage={context()?.percentage ?? 0} />
+ <div class="text-icon-base">
+ <ProgressCircle size={18} percentage={context()?.percentage ?? 0} />
</div>
)
@@ -101,7 +101,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
<Button
type="button"
variant="ghost"
- class="size-6"
+ class="size-7 text-icon-base"
onClick={openContext}
aria-label={language.t("context.usage.view")}
>
diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx
index 3b08652bb..3010cbc40 100644
--- a/packages/app/src/components/settings-general.tsx
+++ b/packages/app/src/components/settings-general.tsx
@@ -10,6 +10,7 @@ import { usePlatform } from "@/context/platform"
import { useSettings, monoFontFamily } from "@/context/settings"
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
import { Link } from "./link"
+import { ScrollFade } from "@opencode-ai/ui/scroll-fade"
let demoSoundState = {
cleanup: undefined as (() => void) | undefined,
@@ -60,24 +61,24 @@ export const SettingsGeneral: Component = () => {
const actions =
platform.update && platform.restart
? [
- {
- label: language.t("toast.update.action.installRestart"),
- onClick: async () => {
- await platform.update!()
- await platform.restart!()
- },
+ {
+ label: language.t("toast.update.action.installRestart"),
+ onClick: async () => {
+ await platform.update!()
+ await platform.restart!()
},
- {
- label: language.t("toast.update.action.notYet"),
- onClick: "dismiss" as const,
- },
- ]
+ },
+ {
+ label: language.t("toast.update.action.notYet"),
+ onClick: "dismiss" as const,
+ },
+ ]
: [
- {
- label: language.t("toast.update.action.notYet"),
- onClick: "dismiss" as const,
- },
- ]
+ {
+ label: language.t("toast.update.action.notYet"),
+ onClick: "dismiss" as const,
+ },
+ ]
showToast({
persistent: true,
@@ -130,7 +131,12 @@ export const SettingsGeneral: Component = () => {
const soundOptions = [...SOUND_OPTIONS]
return (
- <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
+ <ScrollFade
+ direction="vertical"
+ fadeStartSize={0}
+ fadeEndSize={16}
+ class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10"
+ >
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
<div class="flex flex-col gap-1 pt-6 pb-8">
<h2 class="text-16-medium text-text-strong">{language.t("settings.tab.general")}</h2>
@@ -394,7 +400,7 @@ export const SettingsGeneral: Component = () => {
</div>
</div>
</div>
- </div>
+ </ScrollFade>
)
}
diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx
index 393da0c2a..efd18c8bd 100644
--- a/packages/app/src/components/settings-keybinds.tsx
+++ b/packages/app/src/components/settings-keybinds.tsx
@@ -9,6 +9,7 @@ import fuzzysort from "fuzzysort"
import { formatKeybind, parseKeybind, useCommand } from "@/context/command"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
+import { ScrollFade } from "@opencode-ai/ui/scroll-fade"
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
const PALETTE_ID = "command.palette"
@@ -352,7 +353,12 @@ export const SettingsKeybinds: Component = () => {
})
return (
- <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
+ <ScrollFade
+ direction="vertical"
+ fadeStartSize={0}
+ fadeEndSize={16}
+ class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10"
+ >
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
<div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
<div class="flex items-center justify-between gap-4">
@@ -429,6 +435,6 @@ export const SettingsKeybinds: Component = () => {
</div>
</Show>
</div>
- </div>
+ </ScrollFade>
)
}
diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs
index 29ac86f29..d16416c9f 100644
--- a/packages/desktop/src-tauri/src/lib.rs
+++ b/packages/desktop/src-tauri/src/lib.rs
@@ -345,6 +345,7 @@ pub fn run() {
.decorations(false);
let window = window_builder.build().expect("Failed to create window");
+ let _ = window.show();
#[cfg(windows)]
let _ = window.create_overlay_titlebar();
diff --git a/packages/ui/src/components/accordion.css b/packages/ui/src/components/accordion.css
index 7bf287fe5..441bd0542 100644
--- a/packages/ui/src/components/accordion.css
+++ b/packages/ui/src/components/accordion.css
@@ -1,98 +1,107 @@
[data-component="accordion"] {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 8px;
- align-self: stretch;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ align-self: stretch;
- [data-slot="accordion-item"] {
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- align-self: stretch;
- overflow: clip;
+ [data-slot="accordion-item"] {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ align-self: stretch;
+ overflow: clip;
- [data-slot="accordion-header"] {
- width: 100%;
- display: flex;
- align-items: center;
- margin: 0;
- padding: 0;
+ [data-slot="accordion-header"] {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ margin: 0;
+ padding: 0;
- [data-slot="accordion-trigger"] {
- width: 100%;
- display: flex;
- height: 32px;
- padding: 8px 12px;
- justify-content: space-between;
- align-items: center;
- align-self: stretch;
- cursor: default;
- user-select: none;
+ [data-slot="accordion-trigger"] {
+ width: 100%;
+ display: flex;
+ height: 32px;
+ padding: 8px 12px;
+ justify-content: space-between;
+ align-items: center;
+ align-self: stretch;
+ cursor: default;
+ user-select: none;
- background-color: var(--surface-base);
- border: 1px solid var(--border-weak-base);
- border-radius: var(--radius-md);
- overflow: clip;
- color: var(--text-strong);
- transition: background-color 0.15s ease;
+ background-color: var(--surface-base);
+ border: 1px solid var(--border-weak-base);
+ border-radius: var(--radius-md);
+ overflow: clip;
+ color: var(--text-strong);
+ transition-property: background-color, border-color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
- /* text-12-regular */
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 166.667% */
- letter-spacing: var(--letter-spacing-normal);
+ /* text-12-regular */
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 166.667% */
+ letter-spacing: var(--letter-spacing-normal);
- &:hover {
- background-color: var(--surface-base);
- }
- &:focus-visible {
- outline: none;
- }
- &[data-disabled] {
- cursor: not-allowed;
- }
- }
- }
+ &:hover {
+ background-color: var(--surface-base);
+ }
+ &:focus-visible {
+ outline: none;
+ }
+ &[data-disabled] {
+ cursor: not-allowed;
+ }
+ }
+ }
- &[data-expanded] {
- [data-slot="accordion-trigger"] {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
- }
+ [data-slot="accordion-arrow"] {
+ flex-shrink: 0;
+ width: 16px;
+ height: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--text-weak);
+ }
- [data-slot="accordion-content"] {
- border: 1px solid var(--border-weak-base);
- border-top: none;
- border-bottom-left-radius: var(--radius-md);
- border-bottom-right-radius: var(--radius-md);
- }
- }
+ [data-slot="accordion-content"] {
+ display: grid;
+ grid-template-rows: 0fr;
+ transition-property: grid-template-rows, opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ width: 100%;
- [data-slot="accordion-content"] {
- overflow: hidden;
- width: 100%;
- }
- }
-}
+ > * {
+ overflow: hidden;
+ }
+ }
-@keyframes slideDown {
- from {
- height: 0;
- }
- to {
- height: var(--kb-accordion-content-height);
- }
-}
+ [data-slot="accordion-content"][data-expanded] {
+ grid-template-rows: 1fr;
+ }
+
+ [data-slot="accordion-content"][data-closed] {
+ grid-template-rows: 0fr;
+ }
+
+ &[data-expanded] [data-slot="accordion-trigger"] {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ }
-@keyframes slideUp {
- from {
- height: var(--kb-accordion-content-height);
- }
- to {
- height: 0;
- }
+ &[data-expanded] [data-slot="accordion-content"] {
+ border: 1px solid var(--border-weak-base);
+ border-top: none;
+ border-bottom-left-radius: var(--radius-md);
+ border-bottom-right-radius: var(--radius-md);
+ height: auto;
+ }
+ }
}
diff --git a/packages/ui/src/components/accordion.tsx b/packages/ui/src/components/accordion.tsx
index 535d38e3d..e30be95e0 100644
--- a/packages/ui/src/components/accordion.tsx
+++ b/packages/ui/src/components/accordion.tsx
@@ -1,6 +1,7 @@
import { Accordion as Kobalte } from "@kobalte/core/accordion"
-import { splitProps } from "solid-js"
+import { Accessor, createContext, splitProps, useContext } from "solid-js"
import type { ComponentProps, ParentProps } from "solid-js"
+import { MorphChevron } from "./morph-chevron"
export interface AccordionProps extends ComponentProps<typeof Kobalte> {}
export interface AccordionItemProps extends ComponentProps<typeof Kobalte.Item> {}
@@ -8,6 +9,8 @@ export interface AccordionHeaderProps extends ComponentProps<typeof Kobalte.Head
export interface AccordionTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {}
export interface AccordionContentProps extends ComponentProps<typeof Kobalte.Content> {}
+const AccordionItemContext = createContext<Accessor<boolean>>()
+
function AccordionRoot(props: AccordionProps) {
const [split, rest] = splitProps(props, ["class", "classList"])
return (
@@ -22,17 +25,19 @@ function AccordionRoot(props: AccordionProps) {
)
}
-function AccordionItem(props: AccordionItemProps) {
- const [split, rest] = splitProps(props, ["class", "classList"])
+function AccordionItem(props: AccordionItemProps & { expanded?: boolean }) {
+ const [split, rest] = splitProps(props, ["class", "classList", "expanded"])
return (
- <Kobalte.Item
- {...rest}
- data-slot="accordion-item"
- classList={{
- ...(split.classList ?? {}),
- [split.class ?? ""]: !!split.class,
- }}
- />
+ <AccordionItemContext.Provider value={() => split.expanded ?? false}>
+ <Kobalte.Item
+ {...rest}
+ data-slot="accordion-item"
+ classList={{
+ ...(split.classList ?? {}),
+ [split.class ?? ""]: !!split.class,
+ }}
+ />
+ </AccordionItemContext.Provider>
)
}
@@ -84,9 +89,25 @@ function AccordionContent(props: ParentProps<AccordionContentProps>) {
)
}
+export interface AccordionArrowProps extends ComponentProps<"div"> {
+ expanded?: boolean
+}
+
+function AccordionArrow(props: AccordionArrowProps = {}) {
+ const [local, rest] = splitProps(props, ["expanded"])
+ const contextExpanded = useContext(AccordionItemContext)
+ const isExpanded = () => local.expanded ?? contextExpanded?.() ?? false
+ return (
+ <div data-slot="accordion-arrow" {...rest}>
+ <MorphChevron expanded={isExpanded()} />
+ </div>
+ )
+}
+
export const Accordion = Object.assign(AccordionRoot, {
Item: AccordionItem,
Header: AccordionHeader,
Trigger: AccordionTrigger,
Content: AccordionContent,
+ Arrow: AccordionArrow,
})
diff --git a/packages/ui/src/components/avatar.css b/packages/ui/src/components/avatar.css
index 587216077..dff6bb7b8 100644
--- a/packages/ui/src/components/avatar.css
+++ b/packages/ui/src/components/avatar.css
@@ -1,49 +1,49 @@
[data-component="avatar"] {
- --avatar-bg: var(--color-surface-info-base);
- --avatar-fg: var(--color-text-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: var(--avatar-fg);
+ --avatar-bg: var(--color-surface-info-base);
+ --avatar-fg: var(--color-text-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: var(--avatar-fg);
}
[data-component="avatar"][data-has-image] {
- background-color: transparent;
- border: none;
+ background-color: transparent;
+ border: none;
}
[data-component="avatar"][data-size="small"] {
- width: 1.25rem;
- height: 1.25rem;
- font-size: 0.75rem;
- line-height: 1;
+ 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;
+ 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;
+ width: 2rem;
+ height: 2rem;
+ font-size: 1.25rem;
+ line-height: 2rem;
}
[data-component="avatar"] [data-slot="avatar-image"] {
- width: 100%;
- height: 100%;
- display: block;
- object-fit: cover;
- border-radius: inherit;
+ width: 100%;
+ height: 100%;
+ display: block;
+ object-fit: cover;
+ border-radius: inherit;
}
diff --git a/packages/ui/src/components/basic-tool.css b/packages/ui/src/components/basic-tool.css
index 2c6bfeb67..cc58cfa73 100644
--- a/packages/ui/src/components/basic-tool.css
+++ b/packages/ui/src/components/basic-tool.css
@@ -1,97 +1,97 @@
[data-component="tool-trigger"] {
- content-visibility: auto;
- width: 100%;
- display: flex;
- align-items: center;
- align-self: stretch;
- gap: 20px;
- justify-content: space-between;
+ content-visibility: auto;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ align-self: stretch;
+ gap: 20px;
+ justify-content: space-between;
- [data-slot="basic-tool-tool-trigger-content"] {
- width: 100%;
- display: flex;
- align-items: center;
- align-self: stretch;
- gap: 20px;
- }
+ [data-slot="basic-tool-tool-trigger-content"] {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ align-self: stretch;
+ gap: 20px;
+ }
- [data-slot="icon-svg"] {
- flex-shrink: 0;
- }
+ [data-slot="icon-svg"] {
+ flex-shrink: 0;
+ }
- [data-slot="basic-tool-tool-info"] {
- flex-grow: 1;
- min-width: 0;
- }
+ [data-slot="basic-tool-tool-info"] {
+ flex-grow: 1;
+ min-width: 0;
+ }
- [data-slot="basic-tool-tool-info-structured"] {
- width: 100%;
- display: flex;
- align-items: center;
- gap: 8px;
- justify-content: space-between;
- }
+ [data-slot="basic-tool-tool-info-structured"] {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ justify-content: space-between;
+ }
- [data-slot="basic-tool-tool-info-main"] {
- display: flex;
- align-items: center;
- gap: 8px;
- min-width: 0;
- overflow: hidden;
- }
+ [data-slot="basic-tool-tool-info-main"] {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-width: 0;
+ overflow: hidden;
+ }
- [data-slot="basic-tool-tool-title"] {
- flex-shrink: 0;
- 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);
- letter-spacing: var(--letter-spacing-normal);
- color: var(--text-base);
+ [data-slot="basic-tool-tool-title"] {
+ flex-shrink: 0;
+ 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);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-base);
- &.capitalize {
- text-transform: capitalize;
- }
- }
+ &.capitalize {
+ text-transform: capitalize;
+ }
+ }
- [data-slot="basic-tool-tool-subtitle"] {
- flex-shrink: 1;
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- 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);
- letter-spacing: var(--letter-spacing-normal);
- color: var(--text-weak);
+ [data-slot="basic-tool-tool-subtitle"] {
+ flex-shrink: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ 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);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-weak);
- &.clickable {
- cursor: pointer;
- text-decoration: underline;
- transition: color 0.15s ease;
+ &.clickable {
+ cursor: pointer;
+ text-decoration: underline;
+ transition: color 0.15s ease;
- &:hover {
- color: var(--text-base);
- }
- }
- }
+ &:hover {
+ color: var(--text-base);
+ }
+ }
+ }
- [data-slot="basic-tool-tool-arg"] {
- flex-shrink: 1;
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- color: var(--text-weak);
- }
+ [data-slot="basic-tool-tool-arg"] {
+ flex-shrink: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-weak);
+ }
}
diff --git a/packages/ui/src/components/button.css b/packages/ui/src/components/button.css
index d9b345923..56258bd84 100644
--- a/packages/ui/src/components/button.css
+++ b/packages/ui/src/components/button.css
@@ -1,172 +1,168 @@
[data-component="button"] {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- border-style: solid;
- border-width: 1px;
- border-radius: var(--radius-md);
- text-decoration: none;
- user-select: none;
- cursor: default;
- outline: none;
- white-space: nowrap;
-
- &[data-variant="primary"] {
- background-color: var(--button-primary-base);
- border-color: var(--border-weak-base);
- color: var(--icon-invert-base);
-
- [data-slot="icon-svg"] {
- color: var(--icon-invert-base);
- }
-
- &:hover:not(:disabled) {
- background-color: var(--icon-strong-hover);
- }
- &:focus:not(:disabled) {
- background-color: var(--icon-strong-focus);
- }
- &:active:not(:disabled) {
- background-color: var(--icon-strong-active);
- }
- &:disabled {
- background-color: var(--icon-strong-disabled);
-
- [data-slot="icon-svg"] {
- color: var(--icon-invert-base);
- }
- }
- }
-
- &[data-variant="ghost"] {
- border-color: transparent;
- background-color: transparent;
- color: var(--text-strong);
-
- [data-slot="icon-svg"] {
- color: var(--icon-base);
- }
-
- &:hover:not(:disabled) {
- background-color: var(--surface-raised-base-hover);
- }
- &:focus-visible:not(:disabled) {
- background-color: var(--surface-raised-base-hover);
- }
- &:active:not(:disabled) {
- background-color: var(--surface-raised-base-active);
- }
- &:disabled {
- color: var(--text-weak);
- cursor: not-allowed;
-
- [data-slot="icon-svg"] {
- color: var(--icon-disabled);
- }
- }
- &[data-selected="true"]:not(:disabled) {
- background-color: var(--surface-raised-base-hover);
- }
- &[data-active="true"] {
- background-color: var(--surface-raised-base-active);
- }
- }
-
- &[data-variant="secondary"] {
- border: transparent;
- background-color: var(--button-secondary-base);
- color: var(--text-strong);
- box-shadow: var(--shadow-xs-border);
-
- &:hover:not(:disabled) {
- background-color: var(--button-secondary-hover);
- }
- &:focus:not(:disabled) {
- background-color: var(--button-secondary-base);
- }
- &:focus-visible:not(:active) {
- background-color: var(--button-secondary-base);
- box-shadow: var(--shadow-xs-border-focus);
- }
- &:focus-visible:active {
- box-shadow: none;
- }
- &:active:not(:disabled) {
- background-color: var(--button-secondary-base);
- scale: 0.99;
- transition: all 150ms ease-out;
- }
- &:disabled {
- border-color: var(--border-disabled);
- background-color: var(--surface-disabled);
- color: var(--text-weak);
- cursor: not-allowed;
- }
-
- [data-slot="icon-svg"] {
- color: var(--icon-strong-base);
- }
- }
-
- &[data-size="small"] {
- height: 22px;
- padding: 0 8px;
- &[data-icon] {
- padding: 0 12px 0 4px;
- }
-
- font-size: var(--font-size-small);
- line-height: var(--line-height-large);
- gap: 4px;
-
- /* 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="normal"] {
- height: 24px;
- line-height: 24px;
- padding: 0 6px;
- &[data-icon] {
- padding: 0 12px 0 4px;
- }
-
- font-size: var(--font-size-small);
- gap: 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);
- letter-spacing: var(--letter-spacing-normal);
- }
-
- &[data-size="large"] {
- height: 32px;
- padding: 6px 12px;
-
- &[data-icon] {
- padding: 0 12px 0 8px;
- }
-
- gap: 4px;
-
- /* 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);
- }
-
- &:focus {
- outline: none;
- }
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-style: solid;
+ border-width: 1px;
+ border-radius: var(--radius-md);
+ text-decoration: none;
+ user-select: none;
+ cursor: default;
+ padding: 4px 8px;
+ white-space: nowrap;
+ transition-property:
+ background-color, border-color, color, box-shadow, opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ outline: none;
+ line-height: 20px;
+
+ &[data-variant="primary"] {
+ background-color: var(--button-primary-base);
+ border-color: var(--border-weak-base);
+ color: var(--icon-invert-base);
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-invert-base);
+ }
+
+ &:hover:not(:disabled) {
+ background-color: var(--icon-strong-hover);
+ }
+ &:focus:not(:disabled) {
+ background-color: var(--icon-strong-focus);
+ }
+ &:active:not(:disabled) {
+ background-color: var(--icon-strong-active);
+ }
+ &:disabled {
+ background-color: var(--icon-strong-disabled);
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-invert-base);
+ }
+ }
+ }
+
+ &[data-variant="ghost"] {
+ border-color: transparent;
+ background-color: transparent;
+ color: var(--text-strong);
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-base);
+ }
+
+ &:hover:not(:disabled) {
+ background-color: var(--surface-raised-base-hover);
+ }
+ &:focus-visible:not(:disabled) {
+ background-color: var(--surface-raised-base-hover);
+ }
+ &:active:not(:disabled) {
+ background-color: var(--surface-raised-base-active);
+ }
+ &:disabled {
+ color: var(--text-weak);
+ cursor: not-allowed;
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-disabled);
+ }
+ }
+ &[data-selected="true"]:not(:disabled) {
+ background-color: var(--surface-raised-base-hover);
+ }
+ &[data-active="true"] {
+ background-color: var(--surface-raised-base-active);
+ }
+ }
+
+ &[data-variant="secondary"] {
+ border: transparent;
+ background-color: var(--button-secondary-base);
+ color: var(--text-strong);
+ box-shadow: var(--shadow-xs-border);
+
+ &:hover:not(:disabled) {
+ background-color: var(--button-secondary-hover);
+ }
+ &:focus:not(:disabled) {
+ background-color: var(--button-secondary-base);
+ }
+ &:focus-visible:not(:active) {
+ background-color: var(--button-secondary-base);
+ box-shadow: var(--shadow-xs-border-focus);
+ }
+ &:focus-visible:active {
+ box-shadow: none;
+ }
+ &:active:not(:disabled) {
+ background-color: var(--button-secondary-base);
+ scale: 0.99;
+ }
+ &:disabled {
+ border-color: var(--border-disabled);
+ background-color: var(--surface-disabled);
+ color: var(--text-weak);
+ cursor: not-allowed;
+ }
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-strong-base);
+ }
+ }
+
+ &[data-size="small"] {
+ padding: 2px 8px;
+ &[data-icon] {
+ padding: 2px 12px 2px 4px;
+ }
+
+ gap: 4px;
+
+ /* text-12-medium */
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ &[data-size="normal"] {
+ padding: 4px 6px;
+ &[data-icon] {
+ padding: 4px 12px 4px 4px;
+ }
+
+ gap: 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);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ &[data-size="large"] {
+ padding: 6px 12px;
+
+ &[data-icon] {
+ padding: 6px 12px 6px 8px;
+ }
+
+ gap: 4px;
+
+ /* text-14-medium */
+ font-family: var(--font-family-sans);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ &:focus {
+ outline: none;
+ }
}
diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx
index 7f974b2f7..b2d2004d3 100644
--- a/packages/ui/src/components/button.tsx
+++ b/packages/ui/src/components/button.tsx
@@ -4,7 +4,7 @@ import { Icon, IconProps } from "./icon"
export interface ButtonProps
extends ComponentProps<typeof Kobalte>,
- Pick<ComponentProps<"button">, "class" | "classList" | "children"> {
+ Pick<ComponentProps<"button">, "class" | "classList" | "children" | "style"> {
size?: "small" | "normal" | "large"
variant?: "primary" | "secondary" | "ghost"
icon?: IconProps["name"]
diff --git a/packages/ui/src/components/card.css b/packages/ui/src/components/card.css
index 6dae47223..8ac839042 100644
--- a/packages/ui/src/components/card.css
+++ b/packages/ui/src/components/card.css
@@ -1,29 +1,31 @@
[data-component="card"] {
- width: 100%;
- display: flex;
- flex-direction: column;
- background-color: var(--surface-inset-base);
- border: 1px solid var(--border-weaker-base);
- transition: background-color 0.15s ease;
- border-radius: var(--radius-md);
- padding: 6px 12px;
- overflow: clip;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ background-color: var(--surface-inset-base);
+ border: 1px solid var(--border-weaker-base);
+ transition-property: background-color, border-color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ border-radius: var(--radius-md);
+ padding: 6px 12px;
+ overflow: clip;
- &[data-variant="error"] {
- background-color: var(--surface-critical-weak);
- border: 1px solid var(--border-critical-base);
- color: rgba(218, 51, 25, 0.6);
+ &[data-variant="error"] {
+ background-color: var(--surface-critical-weak);
+ border: 1px solid var(--border-critical-base);
+ color: rgba(218, 51, 25, 0.6);
- /* text-12-regular */
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 166.667% */
- letter-spacing: var(--letter-spacing-normal);
+ /* text-12-regular */
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 166.667% */
+ letter-spacing: var(--letter-spacing-normal);
- &[data-component="icon"] {
- color: var(--icon-critical-active);
- }
- }
+ &[data-component="icon"] {
+ color: var(--icon-critical-active);
+ }
+ }
}
diff --git a/packages/ui/src/components/checkbox.css b/packages/ui/src/components/checkbox.css
index b10ebbbd1..44cb1d8ae 100644
--- a/packages/ui/src/components/checkbox.css
+++ b/packages/ui/src/components/checkbox.css
@@ -1,121 +1,136 @@
[data-component="checkbox"] {
- display: flex;
- align-items: center;
- gap: 12px;
- cursor: default;
-
- [data-slot="checkbox-checkbox-input"] {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border-width: 0;
- }
-
- [data-slot="checkbox-checkbox-control"] {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 16px;
- height: 16px;
- padding: 2px;
- aspect-ratio: 1;
- flex-shrink: 0;
- border-radius: var(--radius-sm);
- border: 1px solid var(--border-weak-base);
- /* background-color: var(--surface-weak); */
- }
-
- [data-slot="checkbox-checkbox-indicator"] {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- height: 100%;
- color: var(--icon-base);
- opacity: 0;
- }
-
- /* [data-slot="checkbox-checkbox-content"] { */
- /* } */
-
- [data-slot="checkbox-checkbox-label"] {
- user-select: none;
- color: var(--text-base);
-
- /* text-12-regular */
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 166.667% */
- letter-spacing: var(--letter-spacing-normal);
- }
-
- [data-slot="checkbox-checkbox-description"] {
- color: var(--text-base);
- font-family: var(--font-family-sans);
- font-size: 12px;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-normal);
- letter-spacing: var(--letter-spacing-normal);
- }
-
- [data-slot="checkbox-checkbox-error"] {
- color: var(--text-error);
- font-family: var(--font-family-sans);
- font-size: 12px;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-normal);
- letter-spacing: var(--letter-spacing-normal);
- }
-
- &:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-checkbox-control"] {
- border-color: var(--border-hover);
- background-color: var(--surface-hover);
- }
-
- &:focus-within:not([data-readonly]) [data-slot="checkbox-checkbox-control"] {
- border-color: var(--border-focus);
- box-shadow: 0 0 0 2px var(--surface-focus);
- }
-
- &[data-checked] [data-slot="checkbox-checkbox-control"],
- &[data-indeterminate] [data-slot="checkbox-checkbox-control"] {
- border-color: var(--border-base);
- background-color: var(--surface-weak);
- }
-
- &[data-checked]:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-checkbox-control"],
- &[data-indeterminate]:hover:not([data-disabled]) [data-slot="checkbox-checkbox-control"] {
- border-color: var(--border-hover);
- background-color: var(--surface-hover);
- }
-
- &[data-checked] [data-slot="checkbox-checkbox-indicator"],
- &[data-indeterminate] [data-slot="checkbox-checkbox-indicator"] {
- opacity: 1;
- }
-
- &[data-disabled] {
- cursor: not-allowed;
- }
-
- &[data-disabled] [data-slot="checkbox-checkbox-control"] {
- border-color: var(--border-disabled);
- background-color: var(--surface-disabled);
- }
-
- &[data-invalid] [data-slot="checkbox-checkbox-control"] {
- border-color: var(--border-error);
- }
-
- &[data-readonly] {
- cursor: default;
- pointer-events: none;
- }
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ cursor: default;
+
+ [data-slot="checkbox-checkbox-control"] {
+ transition-property: border-color, background-color, box-shadow;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ }
+
+ [data-slot="checkbox-checkbox-indicator"] {
+ transition-property: opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ }
+
+ [data-slot="checkbox-checkbox-input"] {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+ }
+
+ [data-slot="checkbox-checkbox-control"] {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ padding: 2px;
+ aspect-ratio: 1;
+ flex-shrink: 0;
+ border-radius: var(--radius-sm);
+ border: 1px solid var(--border-weak-base);
+ /* background-color: var(--surface-weak); */
+ }
+
+ [data-slot="checkbox-checkbox-indicator"] {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ color: var(--icon-base);
+ opacity: 0;
+ }
+
+ /* [data-slot="checkbox-checkbox-content"] { */
+ /* } */
+
+ [data-slot="checkbox-checkbox-label"] {
+ user-select: none;
+ color: var(--text-base);
+
+ /* text-12-regular */
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 166.667% */
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ [data-slot="checkbox-checkbox-description"] {
+ color: var(--text-base);
+ font-family: var(--font-family-sans);
+ font-size: 12px;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-normal);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ [data-slot="checkbox-checkbox-error"] {
+ color: var(--text-error);
+ font-family: var(--font-family-sans);
+ font-size: 12px;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-normal);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ &:hover:not([data-disabled], [data-readonly])
+ [data-slot="checkbox-checkbox-control"] {
+ border-color: var(--border-hover);
+ background-color: var(--surface-hover);
+ }
+
+ &:focus-within:not([data-readonly]) [data-slot="checkbox-checkbox-control"] {
+ border-color: var(--border-focus);
+ box-shadow: 0 0 0 2px var(--surface-focus);
+ }
+
+ &[data-checked] [data-slot="checkbox-checkbox-control"],
+ &[data-indeterminate] [data-slot="checkbox-checkbox-control"] {
+ border-color: var(--border-base);
+ background-color: var(--surface-weak);
+ }
+
+ &[data-checked]:hover:not([data-disabled], [data-readonly])
+ [data-slot="checkbox-checkbox-control"],
+ &[data-indeterminate]:hover:not([data-disabled])
+ [data-slot="checkbox-checkbox-control"] {
+ border-color: var(--border-hover);
+ background-color: var(--surface-hover);
+ }
+
+ &[data-checked] [data-slot="checkbox-checkbox-indicator"],
+ &[data-indeterminate] [data-slot="checkbox-checkbox-indicator"] {
+ opacity: 1;
+ }
+
+ &[data-disabled] {
+ cursor: not-allowed;
+ }
+
+ &[data-disabled] [data-slot="checkbox-checkbox-control"] {
+ border-color: var(--border-disabled);
+ background-color: var(--surface-disabled);
+ }
+
+ &[data-invalid] [data-slot="checkbox-checkbox-control"] {
+ border-color: var(--border-error);
+ }
+
+ &[data-readonly] {
+ cursor: default;
+ pointer-events: none;
+ }
}
diff --git a/packages/ui/src/components/code.css b/packages/ui/src/components/code.css
index 671b40512..553219bb8 100644
--- a/packages/ui/src/components/code.css
+++ b/packages/ui/src/components/code.css
@@ -1,4 +1,4 @@
[data-component="code"] {
- content-visibility: auto;
- overflow: hidden;
+ content-visibility: auto;
+ overflow: hidden;
}
diff --git a/packages/ui/src/components/collapsible.css b/packages/ui/src/components/collapsible.css
index 1f20cf85d..312eec84a 100644
--- a/packages/ui/src/components/collapsible.css
+++ b/packages/ui/src/components/collapsible.css
@@ -1,103 +1,99 @@
[data-component="collapsible"] {
- width: 100%;
- display: flex;
- flex-direction: column;
- background-color: var(--surface-inset-base);
- border: 1px solid var(--border-weaker-base);
- transition: background-color 0.15s ease;
- border-radius: var(--radius-md);
- overflow: clip;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ background-color: var(--surface-inset-base);
+ border: 1px solid var(--border-weaker-base);
+ transition-property: background-color, border-color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ border-radius: var(--radius-md);
+ overflow: clip;
- [data-slot="collapsible-trigger"] {
- width: 100%;
- display: flex;
- height: 32px;
- padding: 6px 8px 6px 12px;
- align-items: center;
- align-self: stretch;
- cursor: default;
- user-select: none;
- color: var(--text-base);
+ [data-slot="collapsible-trigger"] {
+ width: 100%;
+ display: flex;
+ height: 32px;
+ padding: 6px 8px 6px 12px;
+ align-items: center;
+ align-self: stretch;
+ cursor: default;
+ user-select: none;
+ color: var(--text-base);
- /* 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);
+ /* 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);
- /* &:hover { */
- /* background-color: var(--surface-base); */
- /* } */
- &:focus-visible {
- outline: none;
- }
- &[data-disabled] {
- cursor: not-allowed;
- }
+ /* &:hover { */
+ /* background-color: var(--surface-base); */
+ /* } */
+ &:focus-visible {
+ outline: none;
+ }
+ &[data-disabled] {
+ cursor: not-allowed;
+ }
- [data-slot="collapsible-arrow"] {
- flex-shrink: 0;
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- }
+ [data-slot="collapsible-arrow"] {
+ flex-shrink: 0;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--text-weak);
+ }
+ }
- [data-slot="collapsible-content"] {
- overflow: hidden;
- /* animation: slideUp 250ms ease-out; */
+ [data-slot="collapsible-content"] {
+ display: grid;
+ grid-template-rows: 0fr;
+ transition-property: grid-template-rows, opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
- /* &[data-expanded] { */
- /* animation: slideDown 250ms ease-out; */
- /* } */
- }
+ > * {
+ overflow: hidden;
+ }
- &[data-variant="ghost"] {
- background-color: transparent;
- border: none;
+ &[data-expanded] {
+ grid-template-rows: 1fr;
+ }
- > [data-slot="collapsible-trigger"] {
- background-color: transparent;
- border: none;
- padding: 0;
+ &[data-closed] {
+ grid-template-rows: 0fr;
+ }
+ }
- /* &:hover { */
- /* color: var(--text-strong); */
- /* } */
- &:focus-visible {
- outline: none;
- }
- &[data-disabled] {
- cursor: not-allowed;
- }
- }
- }
+ &[data-variant="ghost"] {
+ background-color: transparent;
+ border: none;
- &[data-variant="ghost"][data-scope="filetree"] {
- > [data-slot="collapsible-trigger"] {
- height: 24px;
- }
- }
-}
+ > [data-slot="collapsible-trigger"] {
+ background-color: transparent;
+ border: none;
+ padding: 0;
-@keyframes slideDown {
- from {
- height: 0;
- }
- to {
- height: var(--kb-collapsible-content-height);
- }
-}
+ /* &:hover { */
+ /* color: var(--text-strong); */
+ /* } */
+ &:focus-visible {
+ outline: none;
+ }
+ &[data-disabled] {
+ cursor: not-allowed;
+ }
+ }
+ }
-@keyframes slideUp {
- from {
- height: var(--kb-collapsible-content-height);
- }
- to {
- height: 0;
- }
+ &[data-variant="ghost"][data-scope="filetree"] {
+ > [data-slot="collapsible-trigger"] {
+ height: 24px;
+ }
+ }
}
diff --git a/packages/ui/src/components/collapsible.tsx b/packages/ui/src/components/collapsible.tsx
index 903afc308..55b7b6033 100644
--- a/packages/ui/src/components/collapsible.tsx
+++ b/packages/ui/src/components/collapsible.tsx
@@ -1,6 +1,8 @@
import { Collapsible as Kobalte, CollapsibleRootProps } from "@kobalte/core/collapsible"
-import { ComponentProps, ParentProps, splitProps } from "solid-js"
-import { Icon } from "./icon"
+import { Accessor, ComponentProps, createContext, createSignal, ParentProps, splitProps, useContext } from "solid-js"
+import { MorphChevron } from "./morph-chevron"
+
+const CollapsibleContext = createContext<Accessor<boolean>>()
export interface CollapsibleProps extends ParentProps<CollapsibleRootProps> {
class?: string
@@ -9,17 +11,30 @@ export interface CollapsibleProps extends ParentProps<CollapsibleRootProps> {
}
function CollapsibleRoot(props: CollapsibleProps) {
- const [local, others] = splitProps(props, ["class", "classList", "variant"])
+ const [local, others] = splitProps(props, ["class", "classList", "variant", "open", "onOpenChange", "children"])
+ const [internalOpen, setInternalOpen] = createSignal(local.open ?? false)
+
+ const handleOpenChange = (open: boolean) => {
+ setInternalOpen(open)
+ local.onOpenChange?.(open)
+ }
+
return (
- <Kobalte
- data-component="collapsible"
- data-variant={local.variant || "normal"}
- classList={{
- ...(local.classList ?? {}),
- [local.class ?? ""]: !!local.class,
- }}
- {...others}
- />
+ <CollapsibleContext.Provider value={internalOpen}>
+ <Kobalte
+ data-component="collapsible"
+ data-variant={local.variant || "normal"}
+ open={local.open}
+ onOpenChange={handleOpenChange}
+ classList={{
+ ...(local.classList ?? {}),
+ [local.class ?? ""]: !!local.class,
+ }}
+ {...others}
+ >
+ {local.children}
+ </Kobalte>
+ </CollapsibleContext.Provider>
)
}
@@ -32,9 +47,10 @@ function CollapsibleContent(props: ComponentProps<typeof Kobalte.Content>) {
}
function CollapsibleArrow(props?: ComponentProps<"div">) {
+ const isOpen = useContext(CollapsibleContext)
return (
<div data-slot="collapsible-arrow" {...(props || {})}>
- <Icon name="chevron-grabber-vertical" size="small" />
+ <MorphChevron expanded={isOpen?.() ?? false} />
</div>
)
}
diff --git a/packages/ui/src/components/cycle-label.css b/packages/ui/src/components/cycle-label.css
new file mode 100644
index 000000000..46a6408f0
--- /dev/null
+++ b/packages/ui/src/components/cycle-label.css
@@ -0,0 +1,52 @@
+.cycle-label {
+ --c-dur: 200ms;
+ --c-stag: 30ms;
+ --c-ease: cubic-bezier(0.25, 0, 0.5, 1);
+ --c-opacity-start: 0;
+ --c-opacity-end: 1;
+ --c-blur-start: 0px;
+ --c-blur-end: 0px;
+ --c-skew: 10deg;
+
+ display: inline-flex;
+ position: relative;
+
+ transform-style: preserve-3d;
+ perspective: 500px;
+ transition: width 200ms var(--c-ease);
+ will-change: width;
+ overflow: hidden;
+
+ .cycle-char {
+ display: inline-block;
+ transform-style: preserve-3d;
+ min-width: 0.25em;
+ backface-visibility: hidden;
+
+ transition:
+ transform var(--c-dur) var(--c-ease),
+ opacity var(--c-dur) var(--c-ease),
+ filter var(--c-dur) var(--c-ease);
+ transition-delay: calc(var(--i, 0) * var(--c-stag));
+
+ &.enter {
+ opacity: var(--c-opacity-end);
+ filter: blur(var(--c-blur-end));
+ transform: translateY(0) rotateX(0) skewX(0);
+ }
+
+ &.exit {
+ opacity: var(--c-opacity-start);
+ filter: blur(var(--c-blur-start));
+ transform: translateY(50%) rotateX(90deg) skewX(var(--c-skew));
+ }
+
+ &.pre {
+ opacity: var(--c-opacity-start);
+ filter: blur(var(--c-blur-start));
+ transition: none;
+ transform: translateY(-50%) rotateX(-90deg)
+ skewX(calc(var(--c-skew) * -1));
+ }
+ }
+}
diff --git a/packages/ui/src/components/cycle-label.tsx b/packages/ui/src/components/cycle-label.tsx
new file mode 100644
index 000000000..e34385a2c
--- /dev/null
+++ b/packages/ui/src/components/cycle-label.tsx
@@ -0,0 +1,132 @@
+import "./cycle-label.css"
+import { createEffect, createSignal, JSX, on } from "solid-js"
+
+export interface CycleLabelProps extends JSX.HTMLAttributes<HTMLSpanElement> {
+ value: string
+ onValueChange?: (value: string) => void
+ duration?: number | ((value: string) => number)
+ stagger?: number
+ opacity?: [number, number]
+ blur?: [number, number]
+ skewX?: number
+ onAnimationStart?: () => void
+ onAnimationEnd?: () => void
+}
+
+const segmenter =
+ typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter("en", { granularity: "grapheme" }) : null
+
+const getChars = (text: string): string[] =>
+ segmenter ? Array.from(segmenter.segment(text), (s) => s.segment) : text.split("")
+
+const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
+
+export function CycleLabel(props: CycleLabelProps) {
+ const getDuration = (text: string) => {
+ const d = props?.duration ?? 200
+ return typeof d === "function" ? d(text) : d
+ }
+ const stagger = () => props?.stagger ?? 20
+ const opacity = () => props?.opacity ?? [0, 1]
+ const blur = () => props?.blur ?? [0, 0]
+ const skewX = () => props?.skewX ?? 10
+
+ let containerRef: HTMLSpanElement | undefined
+ let isAnimating = false
+ const [currentText, setCurrentText] = createSignal(props.value)
+
+ const setChars = (el: HTMLElement, text: string, state: "enter" | "exit" | "pre" = "enter") => {
+ el.innerHTML = ""
+ const chars = getChars(text)
+ chars.forEach((char, i) => {
+ const span = document.createElement("span")
+ span.textContent = char === " " ? "\u00A0" : char
+ span.className = `cycle-char ${state}`
+ span.style.setProperty("--i", String(i))
+ el.appendChild(span)
+ })
+ }
+
+ const animateToText = async (newText: string) => {
+ if (!containerRef || isAnimating) return
+ if (newText === currentText()) return
+
+ isAnimating = true
+ props.onAnimationStart?.()
+
+ const dur = getDuration(newText)
+ const stag = stagger()
+
+ containerRef.style.width = containerRef.offsetWidth + "px"
+
+ const oldChars = containerRef.querySelectorAll(".cycle-char")
+ oldChars.forEach((c) => c.classList.replace("enter", "exit"))
+
+ const clone = containerRef.cloneNode(false) as HTMLElement
+ Object.assign(clone.style, {
+ position: "absolute",
+ visibility: "hidden",
+ width: "auto",
+ transition: "none",
+ })
+ setChars(clone, newText)
+ document.body.appendChild(clone)
+ const nextWidth = clone.offsetWidth
+ clone.remove()
+
+ const exitTime = oldChars.length * stag + dur
+ await wait(exitTime * 0.3)
+
+ containerRef.style.width = nextWidth + "px"
+
+ const widthDur = 200
+ await wait(widthDur * 0.3)
+
+ setChars(containerRef, newText, "pre")
+ containerRef.offsetWidth
+
+ Array.from(containerRef.children).forEach((c) => (c.className = "cycle-char enter"))
+ setCurrentText(newText)
+ props.onValueChange?.(newText)
+
+ const enterTime = getChars(newText).length * stag + dur
+ await wait(enterTime)
+
+ containerRef.style.width = ""
+ isAnimating = false
+ props.onAnimationEnd?.()
+ }
+
+ createEffect(
+ on(
+ () => props.value,
+ (newValue) => {
+ if (newValue !== currentText()) {
+ animateToText(newValue)
+ }
+ },
+ ),
+ )
+
+ const initRef = (el: HTMLSpanElement) => {
+ containerRef = el
+ setChars(el, props.value)
+ }
+
+ return (
+ <span
+ ref={initRef}
+ class={`cycle-label ${props.class ?? ""}`}
+ style={{
+ "--c-dur": `${getDuration(currentText())}ms`,
+ "--c-stag": `${stagger()}ms`,
+ "--c-opacity-start": opacity()[0],
+ "--c-opacity-end": opacity()[1],
+ "--c-blur-start": `${blur()[0]}px`,
+ "--c-blur-end": `${blur()[1]}px`,
+ "--c-skew": `${skewX()}deg`,
+ ...(typeof props.style === "object" ? props.style : {}),
+ }}
+ />
+ )
+}
diff --git a/packages/ui/src/components/dialog.css b/packages/ui/src/components/dialog.css
index 2e66b644f..5ff9ec595 100644
--- a/packages/ui/src/components/dialog.css
+++ b/packages/ui/src/components/dialog.css
@@ -1,181 +1,196 @@
/* [data-component="dialog-trigger"] { } */
[data-component="dialog-overlay"] {
- position: fixed;
- inset: 0;
- z-index: 50;
- background-color: hsl(from var(--background-base) h s l / 0.2);
+ position: fixed;
+ inset: 0;
+ z-index: 50;
+ background-color: hsl(from var(--background-base) h s l / 0.2);
+
+ animation: overlayHide var(--transition-duration) var(--transition-easing)
+ forwards;
+
+ &[data-expanded] {
+ animation: overlayShow var(--transition-duration) var(--transition-easing)
+ forwards;
+ }
+
+ @starting-style {
+ animation: none;
+ }
}
[data-component="dialog"] {
- position: fixed;
- inset: 0;
- z-index: 50;
- display: flex;
- align-items: center;
- justify-content: center;
- pointer-events: none;
-
- [data-slot="dialog-container"] {
- position: relative;
- z-index: 50;
- width: min(calc(100vw - 16px), 640px);
- height: min(calc(100vh - 16px), 512px);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-items: start;
- overflow: visible;
-
- [data-slot="dialog-content"] {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- align-self: stretch;
- width: 100%;
- max-height: 100%;
- min-height: 280px;
- overflow: auto;
- pointer-events: auto;
-
- /* Hide scrollbar */
- scrollbar-width: none;
- -ms-overflow-style: none;
- &::-webkit-scrollbar {
- display: none;
- }
-
- /* padding: 8px; */
- /* padding: 8px 8px 0 8px; */
- border-radius: var(--radius-xl);
- background: var(--surface-raised-stronger-non-alpha);
- background-clip: padding-box;
- box-shadow: var(--shadow-lg-border-base);
-
- [data-slot="dialog-header"] {
- display: flex;
- padding: 20px;
- justify-content: space-between;
- align-items: center;
- flex-shrink: 0;
- align-self: stretch;
-
- [data-slot="dialog-title"] {
- color: var(--text-strong);
-
- /* text-16-medium */
- font-family: var(--font-family-sans);
- font-size: var(--font-size-large);
- font-style: normal;
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-x-large); /* 150% */
- letter-spacing: var(--letter-spacing-tight);
- }
- /* [data-slot="dialog-close-button"] {} */
- }
-
- [data-slot="dialog-description"] {
- display: flex;
- padding: 16px;
- padding-left: 24px;
- padding-top: 0;
- margin-top: -8px;
- justify-content: space-between;
- align-items: center;
- flex-shrink: 0;
- align-self: stretch;
-
- color: var(--text-base);
-
- /* text-14-regular */
- font-family: var(--font-family-sans);
- font-size: 14px;
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 142.857% */
- letter-spacing: var(--letter-spacing-normal);
- }
-
- [data-slot="dialog-body"] {
- width: 100%;
- position: relative;
- display: flex;
- flex-direction: column;
- flex: 1;
- overflow: hidden;
-
- &:focus-visible {
- outline: none;
- }
- }
- &:focus-visible {
- outline: none;
- }
- }
- }
-
- &[data-fit] {
- [data-slot="dialog-container"] {
- height: auto;
-
- [data-slot="dialog-content"] {
- min-height: 0;
- }
- }
- }
-
- &[data-size="large"] [data-slot="dialog-container"] {
- width: min(calc(100vw - 32px), 800px);
- height: min(calc(100vh - 32px), 600px);
- }
-
- &[data-size="x-large"] [data-slot="dialog-container"] {
- width: min(calc(100vw - 32px), 960px);
- height: min(calc(100vh - 32px), 600px);
- }
+ position: fixed;
+ inset: 0;
+ z-index: 50;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+
+ [data-slot="dialog-container"] {
+ position: relative;
+ z-index: 50;
+ width: min(calc(100vw - 16px), 640px);
+ height: min(calc(100vh - 16px), 512px);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-items: start;
+
+ [data-slot="dialog-content"] {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ align-self: stretch;
+ width: 100%;
+ max-height: 100%;
+ min-height: 280px;
+ pointer-events: auto;
+
+ /* padding: 8px; */
+ /* padding: 8px 8px 0 8px; */
+ border-radius: var(--radius-xl);
+ background: var(--surface-raised-stronger-non-alpha);
+ background-clip: padding-box;
+ box-shadow: var(--shadow-lg-border-base);
+
+ animation: contentHide var(--transition-duration) var(--transition-easing)
+ forwards;
+
+ &[data-expanded] {
+ animation: contentShow var(--transition-duration)
+ var(--transition-easing) forwards;
+ }
+
+ @starting-style {
+ animation: none;
+ }
+
+ [data-slot="dialog-header"] {
+ display: flex;
+ padding: 20px;
+ justify-content: space-between;
+ align-items: center;
+ flex-shrink: 0;
+ align-self: stretch;
+
+ [data-slot="dialog-title"] {
+ color: var(--text-strong);
+
+ /* text-16-medium */
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-large);
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-x-large); /* 150% */
+ letter-spacing: var(--letter-spacing-tight);
+ }
+ /* [data-slot="dialog-close-button"] {} */
+ }
+
+ [data-slot="dialog-description"] {
+ display: flex;
+ padding: 16px;
+ padding-left: 24px;
+ padding-top: 0;
+ margin-top: -8px;
+ justify-content: space-between;
+ align-items: center;
+ flex-shrink: 0;
+ align-self: stretch;
+
+ color: var(--text-base);
+
+ /* text-14-regular */
+ font-family: var(--font-family-sans);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 142.857% */
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ [data-slot="dialog-body"] {
+ width: 100%;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ overflow: hidden;
+
+ &:focus-visible {
+ outline: none;
+ }
+ }
+ &:focus-visible {
+ outline: none;
+ }
+ }
+ }
+
+ &[data-fit] {
+ [data-slot="dialog-container"] {
+ height: auto;
+
+ [data-slot="dialog-content"] {
+ min-height: 0;
+ }
+ }
+ }
+
+ &[data-size="large"] [data-slot="dialog-container"] {
+ width: min(calc(100vw - 32px), 800px);
+ height: min(calc(100vh - 32px), 600px);
+ }
+
+ &[data-size="x-large"] [data-slot="dialog-container"] {
+ width: min(calc(100vw - 32px), 960px);
+ height: min(calc(100vh - 32px), 600px);
+ }
}
[data-component="dialog"][data-transition] [data-slot="dialog-content"] {
- animation: contentHide 100ms ease-in forwards;
+ animation: contentHide 100ms ease-in forwards;
- &[data-expanded] {
- animation: contentShow 150ms ease-out;
- }
+ &[data-expanded] {
+ animation: contentShow 150ms ease-out;
+ }
}
@keyframes overlayShow {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
}
@keyframes overlayHide {
- from {
- opacity: 1;
- }
- to {
- opacity: 0;
- }
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
}
@keyframes contentShow {
- from {
- opacity: 0;
- transform: scale(0.98);
- }
- to {
- opacity: 1;
- transform: scale(1);
- }
+ from {
+ opacity: 0;
+ transform: translateY(2.5%) scale(0.975);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
}
@keyframes contentHide {
- from {
- opacity: 1;
- transform: scale(1);
- }
- to {
- opacity: 0;
- transform: scale(0.98);
- }
+ from {
+ opacity: 1;
+ transform: scale(1);
+ }
+ to {
+ opacity: 0;
+ transform: translateY(-2.5%) scale(0.975);
+ }
}
diff --git a/packages/ui/src/components/diff-changes.css b/packages/ui/src/components/diff-changes.css
index be3cca885..6e0c3d01b 100644
--- a/packages/ui/src/components/diff-changes.css
+++ b/packages/ui/src/components/diff-changes.css
@@ -1,41 +1,41 @@
[data-component="diff-changes"] {
- display: flex;
- gap: 8px;
- justify-content: flex-end;
- align-items: center;
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+ align-items: center;
- [data-slot="diff-changes-additions"] {
- font-family: var(--font-family-mono);
- font-feature-settings: var(--font-family-mono--font-feature-settings);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- text-align: right;
- color: var(--text-diff-add-base);
- }
+ [data-slot="diff-changes-additions"] {
+ font-family: var(--font-family-mono);
+ font-feature-settings: var(--font-family-mono--font-feature-settings);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ text-align: right;
+ color: var(--text-diff-add-base);
+ }
- [data-slot="diff-changes-deletions"] {
- font-family: var(--font-family-mono);
- font-feature-settings: var(--font-family-mono--font-feature-settings);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- text-align: right;
- color: var(--text-diff-delete-base);
- }
+ [data-slot="diff-changes-deletions"] {
+ font-family: var(--font-family-mono);
+ font-feature-settings: var(--font-family-mono--font-feature-settings);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ text-align: right;
+ color: var(--text-diff-delete-base);
+ }
}
[data-component="diff-changes"][data-variant="bars"] {
- width: 18px;
- flex-shrink: 0;
+ width: 18px;
+ flex-shrink: 0;
- svg {
- display: block;
- width: 100%;
- height: auto;
- }
+ svg {
+ display: block;
+ width: 100%;
+ height: auto;
+ }
}
diff --git a/packages/ui/src/components/diff.css b/packages/ui/src/components/diff.css
index 1d94e417a..92c445012 100644
--- a/packages/ui/src/components/diff.css
+++ b/packages/ui/src/components/diff.css
@@ -1,35 +1,35 @@
[data-component="diff"] {
- content-visibility: auto;
+ content-visibility: auto;
- [data-slot="diff-hunk-separator-line-number"] {
- position: sticky;
- left: 0;
- background-color: var(--surface-diff-hidden-strong);
- z-index: 2;
- display: flex;
- align-items: center;
- justify-content: center;
+ [data-slot="diff-hunk-separator-line-number"] {
+ position: sticky;
+ left: 0;
+ background-color: var(--surface-diff-hidden-strong);
+ z-index: 2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
- [data-slot="diff-hunk-separator-line-number-icon"] {
- aspect-ratio: 1;
- width: 24px;
- height: 24px;
- color: var(--icon-strong-base);
- }
- }
- [data-slot="diff-hunk-separator-content"] {
- position: sticky;
- background-color: var(--surface-diff-hidden-base);
- color: var(--text-base);
- width: var(--diffs-column-content-width);
- left: var(--diffs-column-number-width);
- padding-left: 8px;
- user-select: none;
- cursor: default;
- text-align: left;
+ [data-slot="diff-hunk-separator-line-number-icon"] {
+ aspect-ratio: 1;
+ width: 24px;
+ height: 24px;
+ color: var(--icon-strong-base);
+ }
+ }
+ [data-slot="diff-hunk-separator-content"] {
+ position: sticky;
+ background-color: var(--surface-diff-hidden-base);
+ color: var(--text-base);
+ width: var(--diffs-column-content-width);
+ left: var(--diffs-column-number-width);
+ padding-left: 8px;
+ user-select: none;
+ cursor: default;
+ text-align: left;
- [data-slot="diff-hunk-separator-content-span"] {
- mix-blend-mode: var(--text-mix-blend-mode);
- }
- }
+ [data-slot="diff-hunk-separator-content-span"] {
+ mix-blend-mode: var(--text-mix-blend-mode);
+ }
+ }
}
diff --git a/packages/ui/src/components/dropdown-menu.css b/packages/ui/src/components/dropdown-menu.css
index cba041613..cfbfd1cf0 100644
--- a/packages/ui/src/components/dropdown-menu.css
+++ b/packages/ui/src/components/dropdown-menu.css
@@ -1,125 +1,136 @@
[data-component="dropdown-menu-content"],
[data-component="dropdown-menu-sub-content"] {
- min-width: 8rem;
- overflow: hidden;
- border-radius: var(--radius-md);
- border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
- background-clip: padding-box;
- 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);
-
- &:focus,
- &:focus-visible {
- outline: none;
- }
-
- &[data-closed] {
- animation: dropdown-menu-close 0.15s ease-out;
- }
-
- &[data-expanded] {
- animation: dropdown-menu-open 0.15s ease-out;
- }
+ min-width: 8rem;
+ overflow: hidden;
+ border: none;
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-xs-border);
+ background-clip: padding-box;
+ background-color: var(--surface-raised-stronger-non-alpha);
+ padding: 4px;
+ z-index: 100;
+ transform-origin: var(--kb-menu-content-transform-origin);
+
+ &:focus-within,
+ &:focus {
+ outline: none;
+ }
+
+ animation: dropdownMenuContentHide var(--transition-duration)
+ var(--transition-easing) forwards;
+
+ @starting-style {
+ animation: none;
+ }
+
+ &[data-expanded] {
+ pointer-events: auto;
+ animation: dropdownMenuContentShow var(--transition-duration)
+ var(--transition-easing) forwards;
+ }
}
[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);
- }
+ [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;
+ outline: none;
+
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-strong);
+
+ transition-property: background-color, color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ user-select: none;
+
+ &:hover {
+ background-color: 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);
+ outline: none;
+ border: none;
+ }
+ }
+
+ [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 dropdownMenuContentShow {
+ from {
+ opacity: 0;
+ transform: scaleY(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scaleY(1);
+ }
}
-@keyframes dropdown-menu-close {
- from {
- opacity: 1;
- transform: scale(1);
- }
- to {
- opacity: 0;
- transform: scale(0.96);
- }
+@keyframes dropdownMenuContentHide {
+ from {
+ opacity: 1;
+ transform: scaleY(1);
+ }
+ to {
+ opacity: 0;
+ transform: scaleY(0.95);
+ }
}
diff --git a/packages/ui/src/components/file-icon.css b/packages/ui/src/components/file-icon.css
index e650f6dc7..379247f0c 100644
--- a/packages/ui/src/components/file-icon.css
+++ b/packages/ui/src/components/file-icon.css
@@ -1,5 +1,5 @@
[data-component="file-icon"] {
- flex-shrink: 0;
- width: 16px;
- height: 16px;
+ flex-shrink: 0;
+ width: 16px;
+ height: 16px;
}
diff --git a/packages/ui/src/components/hover-card.css b/packages/ui/src/components/hover-card.css
index 02d1f10ad..2a588adc6 100644
--- a/packages/ui/src/components/hover-card.css
+++ b/packages/ui/src/components/hover-card.css
@@ -1,61 +1,63 @@
[data-slot="hover-card-trigger"] {
- display: flex;
- width: 100%;
- min-width: 0;
+ display: flex;
+ width: 100%;
+ min-width: 0;
}
[data-component="hover-card-content"] {
- z-index: 50;
- min-width: 200px;
- max-width: 320px;
- max-height: calc(100vh - 1rem);
- border-radius: 8px;
- background-color: var(--surface-raised-stronger-non-alpha);
- pointer-events: auto;
-
- border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
- background-clip: padding-box;
- box-shadow: var(--shadow-md);
-
- transform-origin: var(--kb-hovercard-content-transform-origin);
-
- &:focus-within {
- outline: none;
- }
-
- &[data-closed] {
- animation: hover-card-close 0.15s ease-out;
- }
-
- &[data-expanded] {
- animation: hover-card-open 0.15s ease-out;
- }
-
- [data-slot="hover-card-body"] {
- padding: 4px;
- max-height: inherit;
- overflow: hidden;
- }
+ z-index: 50;
+ min-width: 200px;
+ max-width: 320px;
+ max-height: calc(100vh - 1rem);
+ border-radius: 8px;
+ background-color: var(--surface-raised-stronger-non-alpha);
+ pointer-events: auto;
+
+ border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
+ background-clip: padding-box;
+ box-shadow: var(--shadow-md);
+
+ transform-origin: var(--kb-hovercard-content-transform-origin);
+
+ &:focus-within {
+ outline: none;
+ }
+
+ &[data-closed] {
+ animation: hover-card-close var(--transition-duration)
+ var(--transition-easing);
+ }
+
+ &[data-expanded] {
+ animation: hover-card-open var(--transition-duration)
+ var(--transition-easing);
+ }
+
+ [data-slot="hover-card-body"] {
+ padding: 4px;
+ max-height: inherit;
+ overflow: hidden;
+ }
}
@keyframes hover-card-open {
- from {
- opacity: 0;
- transform: scale(0.96);
- }
- to {
- opacity: 1;
- transform: scale(1);
- }
+ from {
+ opacity: 0;
+ transform: scale(0.96);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
}
@keyframes hover-card-close {
- from {
- opacity: 1;
- transform: scale(1);
- }
- to {
- opacity: 0;
- transform: scale(0.96);
- }
+ from {
+ opacity: 1;
+ transform: scale(1);
+ }
+ to {
+ opacity: 0;
+ transform: scale(0.96);
+ }
}
diff --git a/packages/ui/src/components/icon-button.css b/packages/ui/src/components/icon-button.css
index aa550e990..94aa80f02 100644
--- a/packages/ui/src/components/icon-button.css
+++ b/packages/ui/src/components/icon-button.css
@@ -1,140 +1,143 @@
[data-component="icon-button"] {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- border-radius: var(--radius-sm);
- text-decoration: none;
- user-select: none;
- aspect-ratio: 1;
- flex-shrink: 0;
-
- &[data-variant="primary"] {
- background-color: var(--icon-strong-base);
-
- [data-slot="icon-svg"] {
- /* color: var(--icon-weak-base); */
- color: var(--icon-invert-base);
-
- /* &:hover:not(:disabled) { */
- /* color: var(--icon-weak-hover); */
- /* } */
- /* &:active:not(:disabled) { */
- /* color: var(--icon-strong-active); */
- /* } */
- }
-
- &:hover:not(:disabled) {
- background-color: var(--icon-strong-hover);
- }
- &:focus:not(:disabled) {
- background-color: var(--icon-strong-focus);
- }
- &:active:not(:disabled) {
- background-color: var(--icon-strong-active);
- }
- &:disabled {
- background-color: var(--icon-strong-disabled);
-
- [data-slot="icon-svg"] {
- color: var(--icon-invert-base);
- }
- }
- }
-
- &[data-variant="secondary"] {
- border: transparent;
- background-color: var(--button-secondary-base);
- color: var(--text-strong);
- box-shadow: var(--shadow-xs-border);
-
- &:hover:not(:disabled) {
- background-color: var(--button-secondary-hover);
- }
- &:focus:not(:disabled) {
- background-color: var(--button-secondary-base);
- }
- &:focus-visible:not(:active) {
- background-color: var(--button-secondary-base);
- box-shadow: var(--shadow-xs-border-focus);
- }
- &:focus-visible:active {
- box-shadow: none;
- }
- &:active:not(:disabled) {
- background-color: var(--button-secondary-base);
- }
-
- [data-slot="icon-svg"] {
- color: var(--icon-strong-base);
- }
-
- &:disabled {
- background-color: var(--icon-strong-disabled);
- color: var(--icon-invert-base);
- cursor: not-allowed;
- }
- }
-
- &[data-variant="ghost"] {
- background-color: transparent;
- /* color: var(--icon-base); */
-
- [data-slot="icon-svg"] {
- color: var(--icon-base);
- }
-
- &:hover:not(:disabled) {
- background-color: var(--surface-raised-base-hover);
-
- /* [data-slot="icon-svg"] { */
- /* color: var(--icon-hover); */
- /* } */
- }
- &:focus-visible:not(:disabled) {
- background-color: var(--surface-raised-base-hover);
- }
- &:active:not(:disabled) {
- background-color: var(--surface-raised-base-active);
- /* [data-slot="icon-svg"] { */
- /* color: var(--icon-active); */
- /* } */
- }
- &:selected:not(:disabled) {
- background-color: var(--surface-raised-base-active);
- /* [data-slot="icon-svg"] { */
- /* color: var(--icon-selected); */
- /* } */
- }
- &:disabled {
- color: var(--icon-invert-base);
- cursor: not-allowed;
- }
- }
-
- &[data-size="normal"] {
- width: 24px;
- height: 24px;
-
- font-size: var(--font-size-small);
- line-height: var(--line-height-large);
- gap: calc(var(--spacing) * 0.5);
- }
-
- &[data-size="large"] {
- height: 32px;
- /* padding: 0 8px 0 6px; */
- gap: 8px;
-
- /* 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);
- }
-
- &:focus {
- outline: none;
- }
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--radius-sm);
+ text-decoration: none;
+ user-select: none;
+ aspect-ratio: 1;
+ flex-shrink: 0;
+ transition-property: background-color, color, opacity, box-shadow;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+
+ &[data-variant="primary"] {
+ background-color: var(--icon-strong-base);
+
+ [data-slot="icon-svg"] {
+ /* color: var(--icon-weak-base); */
+ color: var(--icon-invert-base);
+
+ /* &:hover:not(:disabled) { */
+ /* color: var(--icon-weak-hover); */
+ /* } */
+ /* &:active:not(:disabled) { */
+ /* color: var(--icon-strong-active); */
+ /* } */
+ }
+
+ &:hover:not(:disabled) {
+ background-color: var(--icon-strong-hover);
+ }
+ &:focus:not(:disabled) {
+ background-color: var(--icon-strong-focus);
+ }
+ &:active:not(:disabled) {
+ background-color: var(--icon-strong-active);
+ }
+ &:disabled {
+ background-color: var(--icon-strong-disabled);
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-invert-base);
+ }
+ }
+ }
+
+ &[data-variant="secondary"] {
+ border: transparent;
+ background-color: var(--button-secondary-base);
+ color: var(--text-strong);
+ box-shadow: var(--shadow-xs-border);
+
+ &:hover:not(:disabled) {
+ background-color: var(--button-secondary-hover);
+ }
+ &:focus:not(:disabled) {
+ background-color: var(--button-secondary-base);
+ }
+ &:focus-visible:not(:active) {
+ background-color: var(--button-secondary-base);
+ box-shadow: var(--shadow-xs-border-focus);
+ }
+ &:focus-visible:active {
+ box-shadow: none;
+ }
+ &:active:not(:disabled) {
+ background-color: var(--button-secondary-base);
+ }
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-strong-base);
+ }
+
+ &:disabled {
+ background-color: var(--icon-strong-disabled);
+ color: var(--icon-invert-base);
+ cursor: not-allowed;
+ }
+ }
+
+ &[data-variant="ghost"] {
+ background-color: transparent;
+ /* color: var(--icon-base); */
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-base);
+ }
+
+ &:hover:not(:disabled) {
+ background-color: var(--surface-raised-base-hover);
+
+ /* [data-slot="icon-svg"] { */
+ /* color: var(--icon-hover); */
+ /* } */
+ }
+ &:focus-visible:not(:disabled) {
+ background-color: var(--surface-raised-base-hover);
+ }
+ &:active:not(:disabled) {
+ background-color: var(--surface-raised-base-active);
+ /* [data-slot="icon-svg"] { */
+ /* color: var(--icon-active); */
+ /* } */
+ }
+ &[data-selected]:not(:disabled) {
+ background-color: var(--surface-raised-base-active);
+ /* [data-slot="icon-svg"] { */
+ /* color: var(--icon-selected); */
+ /* } */
+ }
+ &:disabled {
+ color: var(--icon-invert-base);
+ cursor: not-allowed;
+ }
+ }
+
+ &[data-size="normal"] {
+ width: 24px;
+ height: 24px;
+
+ font-size: var(--font-size-small);
+ line-height: var(--line-height-large);
+ gap: calc(var(--spacing) * 0.5);
+ }
+
+ &[data-size="large"] {
+ height: 32px;
+ /* padding: 0 8px 0 6px; */
+ gap: 8px;
+
+ /* 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);
+ }
+
+ &:focus {
+ outline: none;
+ }
}
diff --git a/packages/ui/src/components/icon.css b/packages/ui/src/components/icon.css
index a2ebee30b..dd760ccbc 100644
--- a/packages/ui/src/components/icon.css
+++ b/packages/ui/src/components/icon.css
@@ -1,34 +1,34 @@
[data-component="icon"] {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- /* resize: both; */
- aspect-ratio: 1/1;
- color: var(--icon-base);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ /* resize: both; */
+ aspect-ratio: 1 / 1;
+ color: var(--icon-base);
- &[data-size="small"] {
- width: 16px;
- height: 16px;
- }
+ &[data-size="small"] {
+ width: 16px;
+ height: 16px;
+ }
- &[data-size="normal"] {
- width: 20px;
- height: 20px;
- }
+ &[data-size="normal"] {
+ width: 20px;
+ height: 20px;
+ }
- &[data-size="medium"] {
- width: 24px;
- height: 24px;
- }
+ &[data-size="medium"] {
+ width: 24px;
+ height: 24px;
+ }
- &[data-size="large"] {
- width: 24px;
- height: 24px;
- }
+ &[data-size="large"] {
+ width: 24px;
+ height: 24px;
+ }
- [data-slot="icon-svg"] {
- width: 100%;
- height: auto;
- }
+ [data-slot="icon-svg"] {
+ width: 100%;
+ height: auto;
+ }
}
diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx
index 544c6abdd..f23357293 100644
--- a/packages/ui/src/components/icon.tsx
+++ b/packages/ui/src/components/icon.tsx
@@ -80,13 +80,13 @@ const icons = {
export interface IconProps extends ComponentProps<"svg"> {
name: keyof typeof icons
- size?: "small" | "normal" | "medium" | "large"
+ size?: "small" | "normal" | "medium" | "large" | number
}
export function Icon(props: IconProps) {
const [local, others] = splitProps(props, ["name", "size", "class", "classList"])
return (
- <div data-component="icon" data-size={local.size || "normal"}>
+ <div data-component="icon" data-size={typeof local.size !== 'number' ? local.size || "normal" : `size-[${local.size}px]`}>
<svg
data-slot="icon-svg"
classList={{
diff --git a/packages/ui/src/components/image-preview.css b/packages/ui/src/components/image-preview.css
index 3c47f7a25..b63f60eba 100644
--- a/packages/ui/src/components/image-preview.css
+++ b/packages/ui/src/components/image-preview.css
@@ -1,63 +1,63 @@
[data-component="image-preview"] {
- position: fixed;
- inset: 0;
- z-index: 50;
- display: flex;
- align-items: center;
- justify-content: center;
+ position: fixed;
+ inset: 0;
+ z-index: 50;
+ display: flex;
+ align-items: center;
+ justify-content: center;
- [data-slot="image-preview-container"] {
- position: relative;
- z-index: 50;
- width: min(calc(100vw - 32px), 90vw);
- max-width: 1200px;
- height: min(calc(100vh - 32px), 90vh);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
+ [data-slot="image-preview-container"] {
+ position: relative;
+ z-index: 50;
+ width: min(calc(100vw - 32px), 90vw);
+ max-width: 1200px;
+ height: min(calc(100vh - 32px), 90vh);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
- [data-slot="image-preview-content"] {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 100%;
- max-height: 100%;
- border-radius: var(--radius-lg);
- background: var(--surface-raised-stronger-non-alpha);
- box-shadow:
- 0 15px 45px 0 rgba(19, 16, 16, 0.35),
- 0 3.35px 10.051px 0 rgba(19, 16, 16, 0.25),
- 0 0.998px 2.993px 0 rgba(19, 16, 16, 0.2);
- overflow: hidden;
+ [data-slot="image-preview-content"] {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ max-height: 100%;
+ border-radius: var(--radius-lg);
+ background: var(--surface-raised-stronger-non-alpha);
+ box-shadow:
+ 0 15px 45px 0 rgba(19, 16, 16, 0.35),
+ 0 3.35px 10.051px 0 rgba(19, 16, 16, 0.25),
+ 0 0.998px 2.993px 0 rgba(19, 16, 16, 0.2);
+ overflow: hidden;
- &:focus-visible {
- outline: none;
- }
+ &:focus-visible {
+ outline: none;
+ }
- [data-slot="image-preview-header"] {
- display: flex;
- padding: 8px 8px 0;
- justify-content: flex-end;
- align-items: center;
- align-self: stretch;
- }
+ [data-slot="image-preview-header"] {
+ display: flex;
+ padding: 8px 8px 0;
+ justify-content: flex-end;
+ align-items: center;
+ align-self: stretch;
+ }
- [data-slot="image-preview-body"] {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 16px;
- overflow: auto;
- }
+ [data-slot="image-preview-body"] {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 16px;
+ overflow: auto;
+ }
- [data-slot="image-preview-image"] {
- max-width: 100%;
- max-height: calc(90vh - 100px);
- object-fit: contain;
- border-radius: var(--radius-md);
- }
- }
- }
+ [data-slot="image-preview-image"] {
+ max-width: 100%;
+ max-height: calc(90vh - 100px);
+ object-fit: contain;
+ border-radius: var(--radius-md);
+ }
+ }
+ }
}
diff --git a/packages/ui/src/components/inline-input.css b/packages/ui/src/components/inline-input.css
index 1d8a00e08..8bb0f9afb 100644
--- a/packages/ui/src/components/inline-input.css
+++ b/packages/ui/src/components/inline-input.css
@@ -1,17 +1,17 @@
[data-component="inline-input"] {
- color: inherit;
- background: transparent;
- border: 0;
- border-radius: var(--radius-md);
- padding: 0;
- min-width: 0;
- font: inherit;
- letter-spacing: inherit;
- line-height: inherit;
- box-sizing: border-box;
+ color: inherit;
+ background: transparent;
+ border: 0;
+ border-radius: var(--radius-md);
+ padding: 0;
+ min-width: 0;
+ font: inherit;
+ letter-spacing: inherit;
+ line-height: inherit;
+ box-sizing: border-box;
- &:focus {
- outline: none;
- box-shadow: 0 0 0 1px var(--border-interactive-focus);
- }
+ &:focus {
+ outline: none;
+ box-shadow: 0 0 0 1px var(--border-interactive-focus);
+ }
}
diff --git a/packages/ui/src/components/keybind.css b/packages/ui/src/components/keybind.css
index 1a9e5dce4..c3912ac93 100644
--- a/packages/ui/src/components/keybind.css
+++ b/packages/ui/src/components/keybind.css
@@ -1,18 +1,18 @@
[data-component="keybind"] {
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- height: 20px;
- padding: 0 8px;
- border-radius: 2px;
- background: var(--surface-base);
- box-shadow: var(--shadow-xxs-border);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ height: 20px;
+ padding: 0 8px;
+ border-radius: 2px;
+ background: var(--surface-base);
+ box-shadow: var(--shadow-xxs-border);
- /* text-12-medium */
- font-family: var(--font-family-sans);
- font-size: 12px;
- font-weight: var(--font-weight-medium);
- line-height: 1;
- color: var(--text-weak);
+ /* text-12-medium */
+ font-family: var(--font-family-sans);
+ font-size: 12px;
+ font-weight: var(--font-weight-medium);
+ line-height: 1;
+ color: var(--text-weak);
}
diff --git a/packages/ui/src/components/line-comment.css b/packages/ui/src/components/line-comment.css
index 9dc8eb74f..092461453 100644
--- a/packages/ui/src/components/line-comment.css
+++ b/packages/ui/src/components/line-comment.css
@@ -1,115 +1,117 @@
[data-component="line-comment"] {
- position: absolute;
- right: 24px;
- z-index: var(--line-comment-z, 30);
+ position: absolute;
+ right: 24px;
+ z-index: var(--line-comment-z, 30);
}
[data-component="line-comment"][data-open] {
- z-index: var(--line-comment-open-z, 100);
+ z-index: var(--line-comment-open-z, 100);
}
[data-component="line-comment"] [data-slot="line-comment-button"] {
- width: 20px;
- height: 20px;
- border-radius: var(--radius-md);
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--icon-interactive-base);
- box-shadow: var(--shadow-xs);
- cursor: default;
- border: none;
+ width: 20px;
+ height: 20px;
+ border-radius: var(--radius-md);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--icon-interactive-base);
+ box-shadow: var(--shadow-xs);
+ cursor: default;
+ border: none;
}
[data-component="line-comment"] [data-component="icon"] {
- color: var(--white);
+ color: var(--white);
}
[data-component="line-comment"] [data-slot="line-comment-button"]:focus {
- outline: none;
+ outline: none;
}
-[data-component="line-comment"] [data-slot="line-comment-button"]:focus-visible {
- box-shadow: var(--shadow-xs-border-focus);
+[data-component="line-comment"]
+ [data-slot="line-comment-button"]:focus-visible {
+ box-shadow: var(--shadow-xs-border-focus);
}
[data-component="line-comment"] [data-slot="line-comment-popover"] {
- position: absolute;
- top: calc(100% + 4px);
- right: -8px;
- z-index: var(--line-comment-popover-z, 40);
- min-width: 200px;
- max-width: min(320px, calc(100vw - 48px));
- border-radius: 8px;
- background: var(--surface-raised-stronger-non-alpha);
- box-shadow: var(--shadow-lg-border-base);
- padding: 12px;
+ position: absolute;
+ top: calc(100% + 4px);
+ right: -8px;
+ z-index: var(--line-comment-popover-z, 40);
+ min-width: 200px;
+ max-width: min(320px, calc(100vw - 48px));
+ border-radius: 8px;
+ background: var(--surface-raised-stronger-non-alpha);
+ box-shadow: var(--shadow-lg-border-base);
+ padding: 12px;
}
-[data-component="line-comment"][data-variant="editor"] [data-slot="line-comment-popover"] {
- width: 380px;
- max-width: min(380px, calc(100vw - 48px));
- padding: 8px;
- border-radius: 14px;
+[data-component="line-comment"][data-variant="editor"]
+ [data-slot="line-comment-popover"] {
+ width: 380px;
+ max-width: min(380px, calc(100vw - 48px));
+ padding: 8px;
+ border-radius: 14px;
}
[data-component="line-comment"] [data-slot="line-comment-content"] {
- display: flex;
- flex-direction: column;
- gap: 6px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
}
[data-component="line-comment"] [data-slot="line-comment-text"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-x-large);
- letter-spacing: var(--letter-spacing-normal);
- color: var(--text-strong);
- white-space: pre-wrap;
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-x-large);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-strong);
+ white-space: pre-wrap;
}
[data-component="line-comment"] [data-slot="line-comment-label"],
[data-component="line-comment"] [data-slot="line-comment-editor-label"] {
- 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-weak);
- white-space: nowrap;
+ 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-weak);
+ white-space: nowrap;
}
[data-component="line-comment"] [data-slot="line-comment-editor"] {
- display: flex;
- flex-direction: column;
- gap: 8px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
}
[data-component="line-comment"] [data-slot="line-comment-textarea"] {
- width: 100%;
- resize: vertical;
- padding: 8px;
- border-radius: var(--radius-md);
- background: var(--surface-base);
- border: 1px solid var(--border-base);
- color: var(--text-strong);
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- line-height: var(--line-height-large);
+ width: 100%;
+ resize: vertical;
+ padding: 8px;
+ border-radius: var(--radius-md);
+ background: var(--surface-base);
+ border: 1px solid var(--border-base);
+ color: var(--text-strong);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ line-height: var(--line-height-large);
}
[data-component="line-comment"] [data-slot="line-comment-textarea"]:focus {
- outline: none;
- box-shadow: var(--shadow-xs-border-select);
+ outline: none;
+ box-shadow: var(--shadow-xs-border-select);
}
[data-component="line-comment"] [data-slot="line-comment-actions"] {
- display: flex;
- align-items: center;
- gap: 8px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
}
[data-component="line-comment"] [data-slot="line-comment-editor-label"] {
- margin-right: auto;
+ margin-right: auto;
}
diff --git a/packages/ui/src/components/list.css b/packages/ui/src/components/list.css
index b12d30415..ae99e9841 100644
--- a/packages/ui/src/components/list.css
+++ b/packages/ui/src/components/list.css
@@ -1,331 +1,337 @@
@property --bottom-fade {
- syntax: "<length>";
- inherits: false;
- initial-value: 0px;
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 0px;
}
@keyframes scroll {
- 0% {
- --bottom-fade: 20px;
- }
- 90% {
- --bottom-fade: 20px;
- }
- 100% {
- --bottom-fade: 0;
- }
+ 0% {
+ --bottom-fade: 20px;
+ }
+ 90% {
+ --bottom-fade: 20px;
+ }
+ 100% {
+ --bottom-fade: 0;
+ }
}
[data-component="list"] {
- display: flex;
- flex-direction: column;
- gap: 12px;
- overflow: hidden;
- padding: 0 12px;
-
- [data-slot="list-search-wrapper"] {
- display: flex;
- flex-shrink: 0;
- align-items: center;
- gap: 8px;
- align-self: stretch;
- margin-bottom: 4px;
-
- > [data-component="icon-button"] {
- width: 24px;
- height: 24px;
- flex-shrink: 0;
- background-color: transparent;
- opacity: 0.5;
- transition: opacity 0.15s ease;
-
- &:hover:not(:disabled),
- &:focus-visible:not(:disabled),
- &:active:not(:disabled) {
- background-color: transparent;
- opacity: 0.7;
- }
-
- &:hover:not(:disabled) [data-slot="icon-svg"] {
- color: var(--icon-hover);
- }
-
- &:active:not(:disabled) [data-slot="icon-svg"] {
- color: var(--icon-active);
- }
- }
- }
-
- [data-slot="list-search"] {
- display: flex;
- flex: 1;
- padding: 8px;
- align-items: center;
- gap: 12px;
-
- border-radius: var(--radius-md);
- background: var(--surface-base);
-
- [data-slot="list-search-container"] {
- display: flex;
- align-items: center;
- gap: 8px;
- flex: 1 0 0;
- max-height: 20px;
-
- [data-slot="list-search-input"] {
- width: 100%;
-
- &[data-slot="input-input"] {
- line-height: 20px;
- max-height: 20px;
- }
- }
- }
-
- > [data-component="icon-button"] {
- width: 20px;
- height: 20px;
- background-color: transparent;
- opacity: 0.5;
- transition: opacity 0.15s ease;
-
- &:hover:not(:disabled),
- &:focus-visible:not(:disabled),
- &:active:not(:disabled) {
- background-color: transparent;
- opacity: 0.7;
- }
-
- &:hover:not(:disabled) [data-slot="icon-svg"] {
- color: var(--icon-hover);
- }
-
- &:active:not(:disabled) [data-slot="icon-svg"] {
- color: var(--icon-active);
- }
- }
-
- > [data-component="icon-button"] {
- background-color: transparent;
-
- &:hover:not(:disabled),
- &:focus:not(:disabled),
- &:active:not(:disabled) {
- background-color: transparent;
- }
-
- &:hover:not(:disabled) [data-slot="icon-svg"] {
- color: var(--icon-hover);
- }
-
- &:active:not(:disabled) [data-slot="icon-svg"] {
- color: var(--icon-active);
- }
- }
- }
-
- [data-slot="list-scroll"] {
- display: flex;
- flex-direction: column;
- gap: 12px;
- overflow-y: auto;
- overscroll-behavior: contain;
- mask: linear-gradient(to bottom, #ffff calc(100% - var(--bottom-fade)), #0000);
- animation: scroll;
- animation-timeline: --scroll;
- scroll-timeline: --scroll y;
- scrollbar-width: none;
- -ms-overflow-style: none;
- &::-webkit-scrollbar {
- display: none;
- }
-
- [data-slot="list-empty-state"] {
- display: flex;
- padding: 32px 48px;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- gap: 8px;
- align-self: stretch;
-
- [data-slot="list-message"] {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 2px;
- max-width: 100%;
- color: var(--text-weak);
- white-space: nowrap;
-
- /* text-14-regular */
- font-family: var(--font-family-sans);
- font-size: 14px;
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 142.857% */
- letter-spacing: var(--letter-spacing-normal);
- }
-
- [data-slot="list-filter"] {
- color: var(--text-strong);
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
-
- [data-slot="list-group"] {
- position: relative;
- display: flex;
- flex-direction: column;
-
- &:last-child {
- padding-bottom: 12px;
- }
-
- [data-slot="list-header"] {
- display: flex;
- z-index: 10;
- padding: 8px 12px 8px 8px;
- justify-content: space-between;
- align-items: center;
- align-self: stretch;
- background: var(--surface-raised-stronger-non-alpha);
- position: sticky;
- top: 0;
-
- color: var(--text-weak);
-
- /* 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);
-
- &::after {
- content: "";
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- height: 16px;
- background: linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha), transparent);
- pointer-events: none;
- opacity: 0;
- transition: opacity 0.15s ease;
- }
-
- &[data-stuck="true"]::after {
- opacity: 1;
- }
- }
-
- [data-slot="list-items"] {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- align-self: stretch;
-
- [data-slot="list-item"] {
- display: flex;
- position: relative;
- width: 100%;
- padding: 6px 8px 6px 8px;
- align-items: center;
- color: var(--text-strong);
- scroll-margin-top: 28px;
-
- /* 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-slot="list-item-selected-icon"] {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- aspect-ratio: 1/1;
- [data-component="icon"] {
- color: var(--icon-strong-base);
- }
- }
- [data-slot="list-item-active-icon"] {
- display: none;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- aspect-ratio: 1/1;
- [data-component="icon"] {
- color: var(--icon-strong-base);
- }
- }
-
- [data-slot="list-item-extra-icon"] {
- color: var(--icon-base);
- margin-left: -4px;
- }
-
- [data-slot="list-item-divider"] {
- position: absolute;
- bottom: 0;
- left: var(--list-divider-inset, 16px);
- right: var(--list-divider-inset, 16px);
- height: 1px;
- background: var(--border-weak-base);
- pointer-events: none;
- }
-
- [data-slot="list-item"]:last-child [data-slot="list-item-divider"] {
- display: none;
- }
-
- &[data-active="true"] {
- border-radius: var(--radius-md);
- background: var(--surface-raised-base-hover);
- [data-slot="list-item-active-icon"] {
- display: inline-flex;
- }
- [data-slot="list-item-extra-icon"] {
- display: block !important;
- color: var(--icon-strong-base) !important;
- }
- }
- &:active {
- background: var(--surface-raised-base-active);
- }
- &:focus-visible {
- outline: none;
- }
- }
-
- [data-slot="list-item-add"] {
- display: flex;
- position: relative;
- width: 100%;
- padding: 6px 8px 6px 8px;
- 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-component="input"] {
- width: 100%;
- }
- }
- }
- }
- }
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ overflow: hidden;
+ padding: 0 12px;
+
+ [data-slot="list-search-wrapper"] {
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ gap: 8px;
+ align-self: stretch;
+ margin-bottom: 4px;
+
+ > [data-component="icon-button"] {
+ width: 24px;
+ height: 24px;
+ flex-shrink: 0;
+ background-color: transparent;
+ opacity: 0.5;
+ transition-property: opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+
+ &:hover:not(:disabled),
+ &:focus-visible:not(:disabled),
+ &:active:not(:disabled) {
+ background-color: transparent;
+ opacity: 0.7;
+ }
+
+ &:hover:not(:disabled) [data-slot="icon-svg"] {
+ color: var(--icon-hover);
+ }
+
+ &:active:not(:disabled) [data-slot="icon-svg"] {
+ color: var(--icon-active);
+ }
+ }
+ }
+
+ [data-slot="list-search"] {
+ display: flex;
+ flex: 1;
+ padding: 8px;
+ align-items: center;
+ gap: 12px;
+
+ border-radius: var(--radius-md);
+ background: var(--surface-base);
+
+ [data-slot="list-search-container"] {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex: 1 0 0;
+ max-height: 20px;
+
+ [data-slot="list-search-input"] {
+ width: 100%;
+
+ &[data-slot="input-input"] {
+ line-height: 20px;
+ max-height: 20px;
+ }
+ }
+ }
+
+ > [data-component="icon-button"] {
+ width: 20px;
+ height: 20px;
+ background-color: transparent;
+ opacity: 0.5;
+ transition-property: opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+
+ &:hover:not(:disabled),
+ &:focus-visible:not(:disabled),
+ &:active:not(:disabled) {
+ background-color: transparent;
+ opacity: 0.7;
+ }
+
+ &:hover:not(:disabled) [data-slot="icon-svg"] {
+ color: var(--icon-hover);
+ }
+
+ &:active:not(:disabled) [data-slot="icon-svg"] {
+ color: var(--icon-active);
+ }
+ }
+
+ > [data-component="icon-button"] {
+ background-color: transparent;
+
+ &:hover:not(:disabled),
+ &:focus:not(:disabled),
+ &:active:not(:disabled) {
+ background-color: transparent;
+ }
+
+ &:hover:not(:disabled) [data-slot="icon-svg"] {
+ color: var(--icon-hover);
+ }
+
+ &:active:not(:disabled) [data-slot="icon-svg"] {
+ color: var(--icon-active);
+ }
+ }
+ }
+
+ [data-slot="list-scroll"] {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ overflow-y: auto;
+ overscroll-behavior: contain;
+
+ [data-slot="list-empty-state"] {
+ display: flex;
+ padding: 32px 48px;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+ align-self: stretch;
+
+ [data-slot="list-message"] {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 2px;
+ max-width: 100%;
+ color: var(--text-weak);
+ white-space: nowrap;
+
+ /* text-14-regular */
+ font-family: var(--font-family-sans);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 142.857% */
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ [data-slot="list-filter"] {
+ color: var(--text-strong);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+ [data-slot="list-group"] {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+
+ &:last-child {
+ padding-bottom: 12px;
+ }
+
+ [data-slot="list-header"] {
+ display: flex;
+ z-index: 10;
+ padding: 8px 12px 8px 8px;
+ justify-content: space-between;
+ align-items: center;
+ align-self: stretch;
+ background: var(--surface-raised-stronger-non-alpha);
+ position: sticky;
+ top: 0;
+
+ color: var(--text-weak);
+
+ /* 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);
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ height: 16px;
+ background: linear-gradient(
+ to bottom,
+ var(--surface-raised-stronger-non-alpha),
+ transparent
+ );
+ pointer-events: none;
+ opacity: 0;
+ transition-property: opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ }
+
+ &[data-stuck="true"]::after {
+ opacity: 1;
+ }
+ }
+
+ [data-slot="list-items"] {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ align-self: stretch;
+
+ [data-slot="list-item"] {
+ display: flex;
+ position: relative;
+ width: 100%;
+ padding: 6px 8px 6px 8px;
+ align-items: center;
+ color: var(--text-strong);
+ scroll-margin-top: 28px;
+
+ /* 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-slot="list-item-selected-icon"] {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ aspect-ratio: 1 / 1;
+ [data-component="icon"] {
+ color: var(--icon-strong-base);
+ }
+ }
+
+ [name="check"] {
+ color: var(--icon-strong-base);
+ }
+
+ [data-slot="list-item-active-icon"] {
+ display: none;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ aspect-ratio: 1 / 1;
+ [data-component="icon"] {
+ color: var(--icon-strong-base);
+ }
+ }
+
+ [data-slot="list-item-extra-icon"] {
+ color: var(--icon-base);
+ margin-left: -4px;
+ }
+
+ [data-slot="list-item-divider"] {
+ position: absolute;
+ bottom: 0;
+ left: var(--list-divider-inset, 16px);
+ right: var(--list-divider-inset, 16px);
+ height: 1px;
+ background: var(--border-weak-base);
+ pointer-events: none;
+ }
+
+ [data-slot="list-item"]:last-child [data-slot="list-item-divider"] {
+ display: none;
+ }
+
+ &[data-active="true"] {
+ border-radius: var(--radius-md);
+ background: var(--surface-raised-base-hover);
+ [data-slot="list-item-active-icon"] {
+ display: inline-flex;
+ }
+ [data-slot="list-item-extra-icon"] {
+ display: block !important;
+ color: var(--icon-strong-base) !important;
+ }
+ }
+ &:active {
+ background: var(--surface-raised-base-active);
+ }
+ &:focus-visible {
+ outline: none;
+ }
+ }
+
+ [data-slot="list-item-add"] {
+ display: flex;
+ position: relative;
+ width: 100%;
+ padding: 6px 8px 6px 8px;
+ 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-component="input"] {
+ width: 100%;
+ }
+ }
+ }
+ }
+ }
}
diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx
index 2132897f7..f612a231e 100644
--- a/packages/ui/src/components/list.tsx
+++ b/packages/ui/src/components/list.tsx
@@ -5,6 +5,7 @@ import { useI18n } from "../context/i18n"
import { Icon, type IconProps } from "./icon"
import { IconButton } from "./icon-button"
import { TextField } from "./text-field"
+import { ScrollFade } from "./scroll-fade"
function findByKey(container: HTMLElement, key: string) {
const nodes = container.querySelectorAll<HTMLElement>('[data-slot="list-item"][data-key]')
@@ -267,7 +268,13 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
{searchAction()}
</div>
</Show>
- <div ref={setScrollRef} data-slot="list-scroll">
+ <ScrollFade
+ ref={setScrollRef}
+ direction="vertical"
+ fadeStartSize={0}
+ fadeEndSize={20}
+ data-slot="list-scroll"
+ >
<Show
when={flat().length > 0 || showAdd()}
fallback={
@@ -339,7 +346,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
</div>
</Show>
</Show>
- </div>
+ </ScrollFade>
</div>
)
}
diff --git a/packages/ui/src/components/logo.css b/packages/ui/src/components/logo.css
index a909782b7..b16c441d2 100644
--- a/packages/ui/src/components/logo.css
+++ b/packages/ui/src/components/logo.css
@@ -1,4 +1,4 @@
[data-component="logo-mark"] {
- width: 16px;
- aspect-ratio: 4/5;
+ width: 16px;
+ aspect-ratio: 4 / 5;
}
diff --git a/packages/ui/src/components/markdown.css b/packages/ui/src/components/markdown.css
index ef4318733..5d7f26e3d 100644
--- a/packages/ui/src/components/markdown.css
+++ b/packages/ui/src/components/markdown.css
@@ -1,209 +1,211 @@
[data-component="markdown"] {
- /* Reset & Base Typography */
- min-width: 0;
- max-width: 100%;
- overflow-wrap: break-word;
- color: var(--text-base);
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base); /* 14px */
- line-height: var(--line-height-x-large);
-
- /* Spacing for flow */
- > *:first-child {
- margin-top: 0;
- }
- > *:last-child {
- margin-bottom: 0;
- }
-
- /* Headings: Same size, distinguished by color and spacing */
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- font-size: var(--font-size-base);
- color: var(--text-strong);
- font-weight: var(--font-weight-medium);
- margin-top: 2rem;
- margin-bottom: 0.75rem;
- line-height: var(--line-height-large);
- }
-
- /* Emphasis & Strong: Neutral strong color */
- strong,
- b {
- color: var(--text-strong);
- font-weight: var(--font-weight-medium);
- }
-
- /* Paragraphs */
- p {
- margin-bottom: 1rem;
- }
-
- /* Links */
- a {
- color: var(--text-interactive-base);
- text-decoration: none;
- font-weight: inherit;
- }
-
- a:hover {
- text-decoration: underline;
- text-underline-offset: 2px;
- }
-
- /* Lists */
- ul,
- ol {
- margin-top: 0.5rem;
- margin-bottom: 1rem;
- padding-left: 1.5rem;
- list-style-position: outside;
- }
-
- ul {
- list-style-type: disc;
- }
-
- ol {
- list-style-type: decimal;
- }
-
- li {
- margin-bottom: 0.5rem;
- }
-
- li > p:first-child {
- display: inline;
- margin: 0;
- }
-
- li > p + p {
- display: block;
- margin-top: 0.5rem;
- }
-
- li::marker {
- color: var(--text-weak);
- }
-
- /* Nested lists spacing */
- li > ul,
- li > ol {
- margin-top: 0.25rem;
- margin-bottom: 0.25rem;
- padding-left: 1rem; /* Minimal indent for nesting only */
- }
-
- /* Blockquotes */
- blockquote {
- border-left: 2px solid var(--border-weak-base);
- margin: 1.5rem 0;
- padding-left: 0.5rem;
- color: var(--text-weak);
- font-style: normal;
- }
-
- /* Horizontal Rule - Invisible spacing only */
- hr {
- border: none;
- height: 0;
- margin: 2.5rem 0;
- }
-
- .shiki {
- font-size: 13px;
- padding: 8px 12px;
- border-radius: 4px;
- border: 0.5px solid var(--border-weak-base);
- }
-
- [data-component="markdown-code"] {
- position: relative;
- }
-
- [data-slot="markdown-copy-button"] {
- position: absolute;
- top: 8px;
- right: 8px;
- opacity: 0;
- transition: opacity 0.15s ease;
- z-index: 1;
- }
-
- [data-component="markdown-code"]:hover [data-slot="markdown-copy-button"] {
- opacity: 1;
- }
-
- [data-slot="markdown-copy-button"] [data-slot="check-icon"] {
- display: none;
- }
-
- [data-slot="markdown-copy-button"][data-copied="true"] [data-slot="copy-icon"] {
- display: none;
- }
-
- [data-slot="markdown-copy-button"][data-copied="true"] [data-slot="check-icon"] {
- display: inline-flex;
- }
-
- pre {
- margin-top: 2rem;
- margin-bottom: 2rem;
- overflow: auto;
-
- scrollbar-width: none;
- &::-webkit-scrollbar {
- display: none;
- }
- }
-
- :not(pre) > code {
- font-family: var(--font-family-mono);
- font-feature-settings: var(--font-family-mono--font-feature-settings);
- color: var(--syntax-string);
- font-weight: var(--font-weight-medium);
- /* font-size: 13px; */
-
- /* padding: 2px 2px; */
- /* margin: 0 1.5px; */
- /* border-radius: 2px; */
- /* background: var(--surface-base); */
- /* box-shadow: 0 0 0 0.5px var(--border-weak-base); */
- }
-
- /* Tables */
- table {
- width: 100%;
- border-collapse: collapse;
- margin: 1.5rem 0;
- font-size: var(--font-size-base);
- }
-
- th,
- td {
- /* Minimal borders for structure, matching TUI "lines" roughly but keeping it web-clean */
- border-bottom: 1px solid var(--border-weaker-base);
- padding: 0.75rem 0.5rem;
- text-align: left;
- vertical-align: top;
- }
-
- th {
- color: var(--text-strong);
- font-weight: var(--font-weight-medium);
- border-bottom: 1px solid var(--border-weak-base);
- }
-
- /* Images */
- img {
- max-width: 100%;
- height: auto;
- border-radius: 4px;
- margin: 1.5rem 0;
- display: block;
- }
+ /* Reset & Base Typography */
+ min-width: 0;
+ max-width: 100%;
+ overflow-wrap: break-word;
+ color: var(--text-base);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base); /* 14px */
+ line-height: var(--line-height-x-large);
+
+ /* Spacing for flow */
+ > *:first-child {
+ margin-top: 0;
+ }
+ > *:last-child {
+ margin-bottom: 0;
+ }
+
+ /* Headings: Same size, distinguished by color and spacing */
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ font-size: var(--font-size-base);
+ color: var(--text-strong);
+ font-weight: var(--font-weight-medium);
+ margin-top: 2rem;
+ margin-bottom: 0.75rem;
+ line-height: var(--line-height-large);
+ }
+
+ /* Emphasis & Strong: Neutral strong color */
+ strong,
+ b {
+ color: var(--text-strong);
+ font-weight: var(--font-weight-medium);
+ }
+
+ /* Paragraphs */
+ p {
+ margin-bottom: 1rem;
+ }
+
+ /* Links */
+ a {
+ color: var(--text-interactive-base);
+ text-decoration: none;
+ font-weight: inherit;
+ }
+
+ a:hover {
+ text-decoration: underline;
+ text-underline-offset: 2px;
+ }
+
+ /* Lists */
+ ul,
+ ol {
+ margin-top: 0.5rem;
+ margin-bottom: 1rem;
+ padding-left: 1.5rem;
+ list-style-position: outside;
+ }
+
+ ul {
+ list-style-type: disc;
+ }
+
+ ol {
+ list-style-type: decimal;
+ }
+
+ li {
+ margin-bottom: 0.5rem;
+ }
+
+ li > p:first-child {
+ display: inline;
+ margin: 0;
+ }
+
+ li > p + p {
+ display: block;
+ margin-top: 0.5rem;
+ }
+
+ li::marker {
+ color: var(--text-weak);
+ }
+
+ /* Nested lists spacing */
+ li > ul,
+ li > ol {
+ margin-top: 0.25rem;
+ margin-bottom: 0.25rem;
+ padding-left: 1rem; /* Minimal indent for nesting only */
+ }
+
+ /* Blockquotes */
+ blockquote {
+ border-left: 2px solid var(--border-weak-base);
+ margin: 1.5rem 0;
+ padding-left: 0.5rem;
+ color: var(--text-weak);
+ font-style: normal;
+ }
+
+ /* Horizontal Rule - Invisible spacing only */
+ hr {
+ border: none;
+ height: 0;
+ margin: 2.5rem 0;
+ }
+
+ .shiki {
+ font-size: 13px;
+ padding: 8px 12px;
+ border-radius: 4px;
+ border: 0.5px solid var(--border-weak-base);
+ }
+
+ [data-component="markdown-code"] {
+ position: relative;
+ }
+
+ [data-slot="markdown-copy-button"] {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ z-index: 1;
+ }
+
+ [data-component="markdown-code"]:hover [data-slot="markdown-copy-button"] {
+ opacity: 1;
+ }
+
+ [data-slot="markdown-copy-button"] [data-slot="check-icon"] {
+ display: none;
+ }
+
+ [data-slot="markdown-copy-button"][data-copied="true"]
+ [data-slot="copy-icon"] {
+ display: none;
+ }
+
+ [data-slot="markdown-copy-button"][data-copied="true"]
+ [data-slot="check-icon"] {
+ display: inline-flex;
+ }
+
+ pre {
+ margin-top: 2rem;
+ margin-bottom: 2rem;
+ overflow: auto;
+
+ scrollbar-width: none;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+ }
+
+ :not(pre) > code {
+ font-family: var(--font-family-mono);
+ font-feature-settings: var(--font-family-mono--font-feature-settings);
+ color: var(--syntax-string);
+ font-weight: var(--font-weight-medium);
+ /* font-size: 13px; */
+
+ /* padding: 2px 2px; */
+ /* margin: 0 1.5px; */
+ /* border-radius: 2px; */
+ /* background: var(--surface-base); */
+ /* box-shadow: 0 0 0 0.5px var(--border-weak-base); */
+ }
+
+ /* Tables */
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 1.5rem 0;
+ font-size: var(--font-size-base);
+ }
+
+ th,
+ td {
+ /* Minimal borders for structure, matching TUI "lines" roughly but keeping it web-clean */
+ border-bottom: 1px solid var(--border-weaker-base);
+ padding: 0.75rem 0.5rem;
+ text-align: left;
+ vertical-align: top;
+ }
+
+ th {
+ color: var(--text-strong);
+ font-weight: var(--font-weight-medium);
+ border-bottom: 1px solid var(--border-weak-base);
+ }
+
+ /* Images */
+ img {
+ max-width: 100%;
+ height: auto;
+ border-radius: 4px;
+ margin: 1.5rem 0;
+ display: block;
+ }
}
diff --git a/packages/ui/src/components/message-nav.css b/packages/ui/src/components/message-nav.css
index b1454ad42..6b82dc91c 100644
--- a/packages/ui/src/components/message-nav.css
+++ b/packages/ui/src/components/message-nav.css
@@ -1,122 +1,123 @@
[data-component="message-nav"] {
- flex-shrink: 0;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- padding-left: 0;
- list-style: none;
-
- &[data-size="normal"] {
- width: 240px;
- gap: 4px;
- }
-
- &[data-size="compact"] {
- width: 24px;
- }
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ padding-left: 0;
+ list-style: none;
+
+ &[data-size="normal"] {
+ width: 240px;
+ gap: 4px;
+ }
+
+ &[data-size="compact"] {
+ width: 24px;
+ }
}
[data-slot="message-nav-item"] {
- display: flex;
- align-items: center;
- align-self: stretch;
- justify-content: flex-end;
-
- [data-component="message-nav"][data-size="normal"] & {
- justify-content: flex-start;
- }
+ display: flex;
+ align-items: center;
+ align-self: stretch;
+ justify-content: flex-end;
+
+ [data-component="message-nav"][data-size="normal"] & {
+ justify-content: flex-start;
+ }
}
[data-slot="message-nav-tick-button"] {
- display: flex;
- align-items: center;
- justify-content: flex-start;
- height: 12px;
- width: 24px;
- border: none;
- background: none;
- padding: 0;
-
- &[data-active] [data-slot="message-nav-tick-line"] {
- background-color: var(--icon-strong-base);
- width: 100%;
- }
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ height: 12px;
+ width: 24px;
+ border: none;
+ background: none;
+ padding: 0;
+
+ &[data-active] [data-slot="message-nav-tick-line"] {
+ background-color: var(--icon-strong-base);
+ width: 100%;
+ }
}
[data-slot="message-nav-tick-line"] {
- height: 1px;
- width: 16px;
- background-color: var(--icon-base);
- transition:
- width 0.2s,
- background-color 0.2s;
+ height: 1px;
+ width: 16px;
+ background-color: var(--icon-base);
+ transition:
+ width 0.2s,
+ background-color 0.2s;
}
-[data-slot="message-nav-tick-button"]:hover [data-slot="message-nav-tick-line"] {
- width: 100%;
- background-color: var(--icon-strong-base);
+[data-slot="message-nav-tick-button"]:hover
+ [data-slot="message-nav-tick-line"] {
+ width: 100%;
+ background-color: var(--icon-strong-base);
}
[data-slot="message-nav-message-button"] {
- display: flex;
- align-items: center;
- align-self: stretch;
- width: 100%;
- column-gap: 12px;
- cursor: default;
- border: none;
- background: none;
- padding: 4px 12px;
- border-radius: var(--radius-sm);
+ display: flex;
+ align-items: center;
+ align-self: stretch;
+ width: 100%;
+ column-gap: 12px;
+ cursor: default;
+ border: none;
+ background: none;
+ padding: 4px 12px;
+ border-radius: var(--radius-sm);
}
[data-slot="message-nav-title-preview"] {
- font-size: 14px; /* text-14-regular */
- color: var(--text-base);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
- text-align: left;
-
- &[data-active] {
- color: var(--text-strong);
- }
+ font-size: 14px; /* text-14-regular */
+ color: var(--text-base);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+ text-align: left;
+
+ &[data-active] {
+ color: var(--text-strong);
+ }
}
[data-slot="message-nav-item"]:hover [data-slot="message-nav-message-button"] {
- background-color: var(--surface-base);
+ background-color: var(--surface-base);
}
[data-slot="message-nav-item"]:active [data-slot="message-nav-message-button"] {
- background-color: var(--surface-base-active);
+ background-color: var(--surface-base-active);
}
[data-slot="message-nav-item"]:active [data-slot="message-nav-title-preview"] {
- color: var(--text-base);
+ color: var(--text-base);
}
[data-slot="message-nav-tooltip"] {
- z-index: 1000;
+ z-index: 1000;
}
[data-slot="message-nav-tooltip-content"] {
- display: flex;
- padding: 4px 4px 6px 4px;
- justify-content: center;
- align-items: center;
- border-radius: var(--radius-md);
- background: var(--surface-raised-stronger-non-alpha);
- max-height: calc(100vh - 6rem);
- overflow-y: auto;
-
- /* border/shadow-xs/base */
- box-shadow:
- 0 0 0 1px var(--border-weak-base, rgba(17, 0, 0, 0.12)),
- 0 1px 2px -1px rgba(19, 16, 16, 0.04),
- 0 1px 2px 0 rgba(19, 16, 16, 0.06),
- 0 1px 3px 0 rgba(19, 16, 16, 0.08);
-
- * {
- margin: 0 !important;
- }
+ display: flex;
+ padding: 4px 4px 6px 4px;
+ justify-content: center;
+ align-items: center;
+ border-radius: var(--radius-md);
+ background: var(--surface-raised-stronger-non-alpha);
+ max-height: calc(100vh - 6rem);
+ overflow-y: auto;
+
+ /* border/shadow-xs/base */
+ box-shadow:
+ 0 0 0 1px var(--border-weak-base, rgba(17, 0, 0, 0.12)),
+ 0 1px 2px -1px rgba(19, 16, 16, 0.04),
+ 0 1px 2px 0 rgba(19, 16, 16, 0.06),
+ 0 1px 3px 0 rgba(19, 16, 16, 0.08);
+
+ * {
+ margin: 0 !important;
+ }
}
diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css
index 2bef792a2..dba0a59b7 100644
--- a/packages/ui/src/components/message-part.css
+++ b/packages/ui/src/components/message-part.css
@@ -1,439 +1,439 @@
[data-component="assistant-message"] {
- content-visibility: auto;
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 12px;
+ content-visibility: auto;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
}
[data-component="user-message"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- color: var(--text-base);
- display: flex;
- flex-direction: column;
- gap: 8px;
-
- [data-slot="user-message-attachments"] {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- }
-
- [data-slot="user-message-attachment"] {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- border-radius: 6px;
- overflow: hidden;
- background: var(--surface-weak);
- border: 1px solid var(--border-weak-base);
- transition: border-color 0.15s ease;
-
- &:hover {
- border-color: var(--border-strong-base);
- }
-
- &[data-type="image"] {
- width: 48px;
- height: 48px;
- }
-
- &[data-type="file"] {
- width: 48px;
- height: 48px;
- }
- }
-
- [data-slot="user-message-attachment-image"] {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
-
- [data-slot="user-message-attachment-icon"] {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--icon-weak);
-
- [data-component="icon"] {
- width: 20px;
- height: 20px;
- }
- }
-
- [data-slot="user-message-text"] {
- position: relative;
- white-space: pre-wrap;
- word-break: break-word;
- overflow: hidden;
- background: var(--surface-weak);
- border: 1px solid var(--border-weak-base);
- padding: 8px 12px;
- border-radius: 4px;
-
- [data-highlight="file"] {
- color: var(--syntax-property);
- }
-
- [data-highlight="agent"] {
- color: var(--syntax-type);
- }
-
- [data-slot="user-message-copy-wrapper"] {
- position: absolute;
- top: 7px;
- right: 7px;
- opacity: 0;
- transition: opacity 0.15s ease;
- }
-
- &:hover [data-slot="user-message-copy-wrapper"] {
- opacity: 1;
- }
- }
-
- .text-text-strong {
- color: var(--text-strong);
- }
-
- .font-medium {
- font-weight: var(--font-weight-medium);
- }
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-base);
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ [data-slot="user-message-attachments"] {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+
+ [data-slot="user-message-attachment"] {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6px;
+ overflow: hidden;
+ background: var(--surface-weak);
+ border: 1px solid var(--border-weak-base);
+ transition: border-color 0.15s ease;
+
+ &:hover {
+ border-color: var(--border-strong-base);
+ }
+
+ &[data-type="image"] {
+ width: 48px;
+ height: 48px;
+ }
+
+ &[data-type="file"] {
+ width: 48px;
+ height: 48px;
+ }
+ }
+
+ [data-slot="user-message-attachment-image"] {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ [data-slot="user-message-attachment-icon"] {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--icon-weak);
+
+ [data-component="icon"] {
+ width: 20px;
+ height: 20px;
+ }
+ }
+
+ [data-slot="user-message-text"] {
+ position: relative;
+ white-space: pre-wrap;
+ word-break: break-word;
+ overflow: hidden;
+ background: var(--surface-weak);
+ border: 1px solid var(--border-weak-base);
+ padding: 8px 12px;
+ border-radius: 4px;
+
+ [data-highlight="file"] {
+ color: var(--syntax-property);
+ }
+
+ [data-highlight="agent"] {
+ color: var(--syntax-type);
+ }
+
+ [data-slot="user-message-copy-wrapper"] {
+ position: absolute;
+ top: 7px;
+ right: 7px;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ }
+
+ &:hover [data-slot="user-message-copy-wrapper"] {
+ opacity: 1;
+ }
+ }
+
+ .text-text-strong {
+ color: var(--text-strong);
+ }
+
+ .font-medium {
+ font-weight: var(--font-weight-medium);
+ }
}
[data-component="text-part"] {
- width: 100%;
-
- [data-slot="text-part-body"] {
- position: relative;
- margin-top: 32px;
- }
-
- [data-slot="text-part-copy-wrapper"] {
- position: absolute;
- top: 8px;
- right: 8px;
- opacity: 0;
- transition: opacity 0.15s ease;
- z-index: 1;
- }
-
- [data-slot="text-part-body"]:hover [data-slot="text-part-copy-wrapper"] {
- opacity: 1;
- }
-
- [data-component="markdown"] {
- margin-top: 0;
- font-size: var(--font-size-base);
- }
+ width: 100%;
+
+ [data-slot="text-part-body"] {
+ position: relative;
+ margin-top: 32px;
+ }
+
+ [data-slot="text-part-copy-wrapper"] {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ z-index: 1;
+ }
+
+ [data-slot="text-part-body"]:hover [data-slot="text-part-copy-wrapper"] {
+ opacity: 1;
+ }
+
+ [data-component="markdown"] {
+ margin-top: 0;
+ font-size: var(--font-size-base);
+ }
}
[data-component="reasoning-part"] {
- width: 100%;
- color: var(--text-base);
- opacity: 0.8;
- line-height: var(--line-height-large);
-
- [data-component="markdown"] {
- margin-top: 24px;
- font-style: italic !important;
-
- p:has(strong) {
- margin-top: 24px;
- margin-bottom: 0;
-
- &:first-child {
- margin-top: 0;
- }
- }
- }
+ width: 100%;
+ color: var(--text-base);
+ opacity: 0.8;
+ line-height: var(--line-height-large);
+
+ [data-component="markdown"] {
+ margin-top: 24px;
+ font-style: italic !important;
+
+ p:has(strong) {
+ margin-top: 24px;
+ margin-bottom: 0;
+
+ &:first-child {
+ margin-top: 0;
+ }
+ }
+ }
}
[data-component="tool-error"] {
- display: flex;
- align-items: start;
- gap: 8px;
-
- [data-slot="icon-svg"] {
- color: var(--icon-critical-base);
- margin-top: 4px;
- }
-
- [data-slot="message-part-tool-error-content"] {
- display: flex;
- align-items: start;
- gap: 8px;
- }
-
- [data-slot="message-part-tool-error-title"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- color: var(--text-on-critical-base);
- white-space: nowrap;
- }
-
- [data-slot="message-part-tool-error-message"] {
- color: var(--text-on-critical-weak);
- max-height: 240px;
- overflow-y: auto;
- word-break: break-word;
- }
+ display: flex;
+ align-items: start;
+ gap: 8px;
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-critical-base);
+ margin-top: 4px;
+ }
+
+ [data-slot="message-part-tool-error-content"] {
+ display: flex;
+ align-items: start;
+ gap: 8px;
+ }
+
+ [data-slot="message-part-tool-error-title"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-on-critical-base);
+ white-space: nowrap;
+ }
+
+ [data-slot="message-part-tool-error-message"] {
+ color: var(--text-on-critical-weak);
+ max-height: 240px;
+ overflow-y: auto;
+ word-break: break-word;
+ }
}
[data-component="tool-output"] {
- white-space: pre;
- padding: 8px 12px;
- height: fit-content;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- justify-content: flex-start;
-
- [data-component="markdown"] {
- width: 100%;
- min-width: 0;
-
- pre {
- margin: 0;
- padding: 0;
- background-color: transparent !important;
- border: none !important;
- }
- }
-
- pre {
- margin: 0;
- padding: 0;
- background: none;
- }
-
- &[data-scrollable] {
- height: auto;
- max-height: 240px;
- overflow-y: auto;
- scrollbar-width: none;
- -ms-overflow-style: none;
-
- &::-webkit-scrollbar {
- display: none;
- }
-
- [data-component="markdown"] {
- overflow: visible;
- }
- }
+ white-space: pre;
+ padding: 8px 12px;
+ height: fit-content;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+
+ [data-component="markdown"] {
+ width: 100%;
+ min-width: 0;
+
+ pre {
+ margin: 0;
+ padding: 0;
+ background-color: transparent !important;
+ border: none !important;
+ }
+ }
+
+ pre {
+ margin: 0;
+ padding: 0;
+ background: none;
+ }
+
+ &[data-scrollable] {
+ height: auto;
+ max-height: 240px;
+ overflow-y: auto;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ [data-component="markdown"] {
+ overflow: visible;
+ }
+ }
}
[data-component="edit-trigger"],
[data-component="write-trigger"] {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- width: 100%;
-
- [data-slot="message-part-title-area"] {
- flex-grow: 1;
- display: flex;
- align-items: center;
- gap: 8px;
- min-width: 0;
- }
-
- [data-slot="message-part-title"] {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- gap: 4px;
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- color: var(--text-base);
- }
-
- [data-slot="message-part-title-text"] {
- text-transform: capitalize;
- }
-
- [data-slot="message-part-title-filename"] {
- /* No text-transform - preserve original filename casing */
- }
-
- [data-slot="message-part-path"] {
- display: flex;
- flex-grow: 1;
- min-width: 0;
- }
-
- [data-slot="message-part-directory"] {
- color: var(--text-weak);
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- direction: rtl;
- text-align: left;
- }
-
- [data-slot="message-part-filename"] {
- color: var(--text-strong);
- flex-shrink: 0;
- }
-
- [data-slot="message-part-actions"] {
- display: flex;
- gap: 16px;
- align-items: center;
- justify-content: flex-end;
- }
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ width: 100%;
+
+ [data-slot="message-part-title-area"] {
+ flex-grow: 1;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-width: 0;
+ }
+
+ [data-slot="message-part-title"] {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-base);
+ }
+
+ [data-slot="message-part-title-text"] {
+ text-transform: capitalize;
+ }
+
+ [data-slot="message-part-title-filename"] {
+ /* No text-transform - preserve original filename casing */
+ }
+
+ [data-slot="message-part-path"] {
+ display: flex;
+ flex-grow: 1;
+ min-width: 0;
+ }
+
+ [data-slot="message-part-directory"] {
+ color: var(--text-weak);
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ direction: rtl;
+ text-align: left;
+ }
+
+ [data-slot="message-part-filename"] {
+ color: var(--text-strong);
+ flex-shrink: 0;
+ }
+
+ [data-slot="message-part-actions"] {
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ justify-content: flex-end;
+ }
}
[data-component="edit-content"] {
- border-top: 1px solid var(--border-weaker-base);
- max-height: 420px;
- overflow-y: auto;
+ border-top: 1px solid var(--border-weaker-base);
+ max-height: 420px;
+ overflow-y: auto;
- scrollbar-width: none;
- -ms-overflow-style: none;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
- &::-webkit-scrollbar {
- display: none;
- }
+ &::-webkit-scrollbar {
+ display: none;
+ }
}
[data-component="write-content"] {
- border-top: 1px solid var(--border-weaker-base);
- max-height: 240px;
- overflow-y: auto;
+ border-top: 1px solid var(--border-weaker-base);
+ max-height: 240px;
+ overflow-y: auto;
- [data-component="code"] {
- padding-bottom: 0px !important;
- }
+ [data-component="code"] {
+ padding-bottom: 0px !important;
+ }
- /* Hide scrollbar */
- scrollbar-width: none;
- -ms-overflow-style: none;
+ /* Hide scrollbar */
+ scrollbar-width: none;
+ -ms-overflow-style: none;
- &::-webkit-scrollbar {
- display: none;
- }
+ &::-webkit-scrollbar {
+ display: none;
+ }
}
[data-component="tool-action"] {
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
[data-component="todos"] {
- padding: 10px 12px 24px 48px;
- display: flex;
- flex-direction: column;
- gap: 8px;
-
- [data-slot="message-part-todo-content"] {
- &[data-completed="completed"] {
- text-decoration: line-through;
- color: var(--text-weaker);
- }
- }
+ padding: 10px 12px 24px 48px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ [data-slot="message-part-todo-content"] {
+ &[data-completed="completed"] {
+ text-decoration: line-through;
+ color: var(--text-weaker);
+ }
+ }
}
[data-component="task-tools"] {
- padding: 8px 12px;
- display: flex;
- flex-direction: column;
- gap: 6px;
-
- [data-slot="task-tool-item"] {
- display: flex;
- align-items: center;
- gap: 8px;
- color: var(--text-weak);
-
- [data-slot="icon-svg"] {
- flex-shrink: 0;
- color: var(--icon-weak);
- }
- }
-
- [data-slot="task-tool-title"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large);
- color: var(--text-weak);
- }
-
- [data-slot="task-tool-subtitle"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large);
- color: var(--text-weaker);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
+ padding: 8px 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+
+ [data-slot="task-tool-item"] {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: var(--text-weak);
+
+ [data-slot="icon-svg"] {
+ flex-shrink: 0;
+ color: var(--icon-weak);
+ }
+ }
+
+ [data-slot="task-tool-title"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ color: var(--text-weak);
+ }
+
+ [data-slot="task-tool-subtitle"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large);
+ color: var(--text-weaker);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
}
[data-component="diagnostics"] {
- display: flex;
- flex-direction: column;
- gap: 4px;
- padding: 8px 12px;
- background-color: var(--surface-critical-weak);
- border-top: 1px solid var(--border-critical-base);
-
- [data-slot="diagnostic"] {
- display: flex;
- align-items: baseline;
- gap: 6px;
- font-family: var(--font-family-mono);
- font-size: var(--font-size-small);
- line-height: var(--line-height-large);
- }
-
- [data-slot="diagnostic-label"] {
- color: var(--text-on-critical-base);
- font-weight: var(--font-weight-medium);
- text-transform: uppercase;
- letter-spacing: -0.5px;
- flex-shrink: 0;
- }
-
- [data-slot="diagnostic-location"] {
- color: var(--text-on-critical-weak);
- flex-shrink: 0;
- }
-
- [data-slot="diagnostic-message"] {
- color: var(--text-on-critical-base);
- word-break: break-word;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 3;
- line-clamp: 3;
- overflow: hidden;
- }
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 8px 12px;
+ background-color: var(--surface-critical-weak);
+ border-top: 1px solid var(--border-critical-base);
+
+ [data-slot="diagnostic"] {
+ display: flex;
+ align-items: baseline;
+ gap: 6px;
+ font-family: var(--font-family-mono);
+ font-size: var(--font-size-small);
+ line-height: var(--line-height-large);
+ }
+
+ [data-slot="diagnostic-label"] {
+ color: var(--text-on-critical-base);
+ font-weight: var(--font-weight-medium);
+ text-transform: uppercase;
+ letter-spacing: -0.5px;
+ flex-shrink: 0;
+ }
+
+ [data-slot="diagnostic-location"] {
+ color: var(--text-on-critical-weak);
+ flex-shrink: 0;
+ }
+
+ [data-slot="diagnostic-message"] {
+ color: var(--text-on-critical-base);
+ word-break: break-word;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 3;
+ line-clamp: 3;
+ overflow: hidden;
+ }
}
[data-component="user-message"] [data-slot="user-message-text"],
@@ -446,383 +446,383 @@
[data-component="todos"],
[data-component="diagnostics"],
.error-card {
- -webkit-user-select: text;
- user-select: text;
+ -webkit-user-select: text;
+ user-select: text;
}
[data-component="tool-part-wrapper"] {
- width: 100%;
-
- &[data-permission="true"],
- &[data-question="true"] {
- position: sticky;
- top: calc(2px + var(--sticky-header-height, 40px));
- bottom: 0px;
- z-index: 20;
- border-radius: 6px;
- border: none;
- box-shadow: var(--shadow-xs-border-base);
- background-color: var(--surface-raised-base);
- overflow: visible;
- overflow-anchor: none;
-
- & > *:first-child {
- border-top-left-radius: 6px;
- border-top-right-radius: 6px;
- overflow: hidden;
- }
-
- & > *:last-child {
- border-bottom-left-radius: 6px;
- border-bottom-right-radius: 6px;
- overflow: hidden;
- }
-
- [data-component="collapsible"] {
- border: none;
- }
-
- [data-component="card"] {
- border: none;
- }
- }
-
- &[data-permission="true"] {
- &::before {
- content: "";
- position: absolute;
- inset: -1.5px;
- top: -5px;
- border-radius: 7.5px;
- border: 1.5px solid transparent;
- background:
- linear-gradient(var(--background-base) 0 0) padding-box,
- conic-gradient(
- from var(--border-angle),
- transparent 0deg,
- transparent 0deg,
- var(--border-warning-strong, var(--border-warning-selected)) 300deg,
- var(--border-warning-base) 360deg
- )
- border-box;
- animation: chase-border 2.5s linear infinite;
- pointer-events: none;
- z-index: -1;
- }
- }
-
- &[data-question="true"] {
- background: var(--background-base);
- border: 1px solid var(--border-weak-base);
- }
+ width: 100%;
+
+ &[data-permission="true"],
+ &[data-question="true"] {
+ position: sticky;
+ top: calc(2px + var(--sticky-header-height, 40px));
+ bottom: 0px;
+ z-index: 20;
+ border-radius: 6px;
+ border: none;
+ box-shadow: var(--shadow-xs-border-base);
+ background-color: var(--surface-raised-base);
+ overflow: visible;
+ overflow-anchor: none;
+
+ & > *:first-child {
+ border-top-left-radius: 6px;
+ border-top-right-radius: 6px;
+ overflow: hidden;
+ }
+
+ & > *:last-child {
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+ overflow: hidden;
+ }
+
+ [data-component="collapsible"] {
+ border: none;
+ }
+
+ [data-component="card"] {
+ border: none;
+ }
+ }
+
+ &[data-permission="true"] {
+ &::before {
+ content: "";
+ position: absolute;
+ inset: -1.5px;
+ top: -5px;
+ border-radius: 7.5px;
+ border: 1.5px solid transparent;
+ background:
+ linear-gradient(var(--background-base) 0 0) padding-box,
+ conic-gradient(
+ from var(--border-angle),
+ transparent 0deg,
+ transparent 0deg,
+ var(--border-warning-strong, var(--border-warning-selected)) 300deg,
+ var(--border-warning-base) 360deg
+ )
+ border-box;
+ animation: chase-border 2.5s linear infinite;
+ pointer-events: none;
+ z-index: -1;
+ }
+ }
+
+ &[data-question="true"] {
+ background: var(--background-base);
+ border: 1px solid var(--border-weak-base);
+ }
}
@property --border-angle {
- syntax: "<angle>";
- initial-value: 0deg;
- inherits: false;
+ syntax: "<angle>";
+ initial-value: 0deg;
+ inherits: false;
}
@keyframes chase-border {
- from {
- --border-angle: 0deg;
- }
+ from {
+ --border-angle: 0deg;
+ }
- to {
- --border-angle: 360deg;
- }
+ to {
+ --border-angle: 360deg;
+ }
}
[data-component="permission-prompt"] {
- display: flex;
- flex-direction: column;
- padding: 8px 12px;
- background-color: var(--surface-raised-strong);
- border-radius: 0 0 6px 6px;
-
- [data-slot="permission-actions"] {
- display: flex;
- align-items: center;
- gap: 8px;
- justify-content: flex-end;
- }
+ display: flex;
+ flex-direction: column;
+ padding: 8px 12px;
+ background-color: var(--surface-raised-strong);
+ border-radius: 0 0 6px 6px;
+
+ [data-slot="permission-actions"] {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ justify-content: flex-end;
+ }
}
[data-component="question-prompt"] {
- display: flex;
- flex-direction: column;
- padding: 12px;
- background-color: var(--surface-inset-base);
- border-radius: 0 0 6px 6px;
- gap: 12px;
-
- [data-slot="question-tabs"] {
- display: flex;
- gap: 4px;
- flex-wrap: wrap;
-
- [data-slot="question-tab"] {
- padding: 4px 12px;
- font-size: 13px;
- border-radius: 4px;
- background-color: var(--surface-base);
- color: var(--text-base);
- border: none;
- cursor: pointer;
- transition:
- color 0.15s,
- background-color 0.15s;
-
- &:hover {
- background-color: var(--surface-base-hover);
- }
-
- &[data-active="true"] {
- background-color: var(--surface-raised-base);
- }
-
- &[data-answered="true"] {
- color: var(--text-strong);
- }
- }
- }
-
- [data-slot="question-content"] {
- display: flex;
- flex-direction: column;
- gap: 8px;
-
- [data-slot="question-text"] {
- font-size: 14px;
- color: var(--text-base);
- line-height: 1.5;
- }
- }
-
- [data-slot="question-options"] {
- display: flex;
- flex-direction: column;
- gap: 4px;
-
- [data-slot="question-option"] {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 2px;
- padding: 8px 12px;
- background-color: var(--surface-base);
- border: 1px solid var(--border-weaker-base);
- border-radius: 6px;
- cursor: pointer;
- text-align: left;
- width: 100%;
- transition:
- background-color 0.15s,
- border-color 0.15s;
- position: relative;
-
- &:hover {
- background-color: var(--surface-base-hover);
- border-color: var(--border-default);
- }
-
- &[data-picked="true"] {
- [data-component="icon"] {
- position: absolute;
- right: 12px;
- top: 50%;
- transform: translateY(-50%);
- color: var(--text-strong);
- }
- }
-
- [data-slot="option-label"] {
- font-size: 14px;
- color: var(--text-base);
- font-weight: 500;
- }
-
- [data-slot="option-description"] {
- font-size: 12px;
- color: var(--text-weak);
- }
- }
-
- [data-slot="custom-input-form"] {
- display: flex;
- gap: 8px;
- padding: 8px 0;
- align-items: stretch;
-
- [data-slot="custom-input"] {
- flex: 1;
- padding: 8px 12px;
- font-size: 14px;
- border: 1px solid var(--border-default);
- border-radius: 6px;
- background-color: var(--surface-base);
- color: var(--text-base);
- outline: none;
-
- &:focus {
- border-color: var(--border-focus);
- }
-
- &::placeholder {
- color: var(--text-weak);
- }
- }
-
- [data-component="button"] {
- height: auto;
- }
- }
- }
-
- [data-slot="question-review"] {
- display: flex;
- flex-direction: column;
- gap: 12px;
-
- [data-slot="review-title"] {
- display: none;
- }
-
- [data-slot="review-item"] {
- display: flex;
- flex-direction: column;
- gap: 2px;
- font-size: 13px;
-
- [data-slot="review-label"] {
- color: var(--text-weak);
- }
-
- [data-slot="review-value"] {
- color: var(--text-strong);
-
- &[data-answered="false"] {
- color: var(--text-weak);
- }
- }
- }
- }
-
- [data-slot="question-actions"] {
- display: flex;
- align-items: center;
- gap: 8px;
- justify-content: flex-end;
- }
+ display: flex;
+ flex-direction: column;
+ padding: 12px;
+ background-color: var(--surface-inset-base);
+ border-radius: 0 0 6px 6px;
+ gap: 12px;
+
+ [data-slot="question-tabs"] {
+ display: flex;
+ gap: 4px;
+ flex-wrap: wrap;
+
+ [data-slot="question-tab"] {
+ padding: 4px 12px;
+ font-size: 13px;
+ border-radius: 4px;
+ background-color: var(--surface-base);
+ color: var(--text-base);
+ border: none;
+ cursor: pointer;
+ transition:
+ color 0.15s,
+ background-color 0.15s;
+
+ &:hover {
+ background-color: var(--surface-base-hover);
+ }
+
+ &[data-active="true"] {
+ background-color: var(--surface-raised-base);
+ }
+
+ &[data-answered="true"] {
+ color: var(--text-strong);
+ }
+ }
+ }
+
+ [data-slot="question-content"] {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ [data-slot="question-text"] {
+ font-size: 14px;
+ color: var(--text-base);
+ line-height: 1.5;
+ }
+ }
+
+ [data-slot="question-options"] {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ [data-slot="question-option"] {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 2px;
+ padding: 8px 12px;
+ background-color: var(--surface-base);
+ border: 1px solid var(--border-weaker-base);
+ border-radius: 6px;
+ cursor: pointer;
+ text-align: left;
+ width: 100%;
+ transition:
+ background-color 0.15s,
+ border-color 0.15s;
+ position: relative;
+
+ &:hover {
+ background-color: var(--surface-base-hover);
+ border-color: var(--border-default);
+ }
+
+ &[data-picked="true"] {
+ [data-component="icon"] {
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--text-strong);
+ }
+ }
+
+ [data-slot="option-label"] {
+ font-size: 14px;
+ color: var(--text-base);
+ font-weight: 500;
+ }
+
+ [data-slot="option-description"] {
+ font-size: 12px;
+ color: var(--text-weak);
+ }
+ }
+
+ [data-slot="custom-input-form"] {
+ display: flex;
+ gap: 8px;
+ padding: 8px 0;
+ align-items: stretch;
+
+ [data-slot="custom-input"] {
+ flex: 1;
+ padding: 8px 12px;
+ font-size: 14px;
+ border: 1px solid var(--border-default);
+ border-radius: 6px;
+ background-color: var(--surface-base);
+ color: var(--text-base);
+ outline: none;
+
+ &:focus {
+ border-color: var(--border-focus);
+ }
+
+ &::placeholder {
+ color: var(--text-weak);
+ }
+ }
+
+ [data-component="button"] {
+ height: auto;
+ }
+ }
+ }
+
+ [data-slot="question-review"] {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+
+ [data-slot="review-title"] {
+ display: none;
+ }
+
+ [data-slot="review-item"] {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ font-size: 13px;
+
+ [data-slot="review-label"] {
+ color: var(--text-weak);
+ }
+
+ [data-slot="review-value"] {
+ color: var(--text-strong);
+
+ &[data-answered="false"] {
+ color: var(--text-weak);
+ }
+ }
+ }
+ }
+
+ [data-slot="question-actions"] {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ justify-content: flex-end;
+ }
}
[data-component="question-answers"] {
- display: flex;
- flex-direction: column;
- gap: 12px;
- padding: 8px 12px;
-
- [data-slot="question-answer-item"] {
- display: flex;
- flex-direction: column;
- gap: 2px;
- font-size: 13px;
-
- [data-slot="question-text"] {
- color: var(--text-weak);
- }
-
- [data-slot="answer-text"] {
- color: var(--text-strong);
- }
- }
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding: 8px 12px;
+
+ [data-slot="question-answer-item"] {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ font-size: 13px;
+
+ [data-slot="question-text"] {
+ color: var(--text-weak);
+ }
+
+ [data-slot="answer-text"] {
+ color: var(--text-strong);
+ }
+ }
}
[data-component="apply-patch-files"] {
- display: flex;
- flex-direction: column;
+ display: flex;
+ flex-direction: column;
}
[data-component="apply-patch-file"] {
- display: flex;
- flex-direction: column;
- border-top: 1px solid var(--border-weaker-base);
-
- &:first-child {
- border-top: 1px solid var(--border-weaker-base);
- }
-
- [data-slot="apply-patch-file-header"] {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 8px 12px;
- background-color: var(--surface-inset-base);
- }
-
- [data-slot="apply-patch-file-action"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large);
- color: var(--text-base);
- flex-shrink: 0;
-
- &[data-type="delete"] {
- color: var(--text-critical-base);
- }
-
- &[data-type="add"] {
- color: var(--text-success-base);
- }
-
- &[data-type="move"] {
- color: var(--text-warning-base);
- }
- }
-
- [data-slot="apply-patch-file-path"] {
- font-family: var(--font-family-mono);
- font-size: var(--font-size-small);
- color: var(--text-weak);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex-grow: 1;
- }
-
- [data-slot="apply-patch-deletion-count"] {
- font-family: var(--font-family-mono);
- font-size: var(--font-size-small);
- color: var(--text-critical-base);
- flex-shrink: 0;
- }
+ display: flex;
+ flex-direction: column;
+ border-top: 1px solid var(--border-weaker-base);
+
+ &:first-child {
+ border-top: 1px solid var(--border-weaker-base);
+ }
+
+ [data-slot="apply-patch-file-header"] {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ background-color: var(--surface-inset-base);
+ }
+
+ [data-slot="apply-patch-file-action"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ color: var(--text-base);
+ flex-shrink: 0;
+
+ &[data-type="delete"] {
+ color: var(--text-critical-base);
+ }
+
+ &[data-type="add"] {
+ color: var(--text-success-base);
+ }
+
+ &[data-type="move"] {
+ color: var(--text-warning-base);
+ }
+ }
+
+ [data-slot="apply-patch-file-path"] {
+ font-family: var(--font-family-mono);
+ font-size: var(--font-size-small);
+ color: var(--text-weak);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex-grow: 1;
+ }
+
+ [data-slot="apply-patch-deletion-count"] {
+ font-family: var(--font-family-mono);
+ font-size: var(--font-size-small);
+ color: var(--text-critical-base);
+ flex-shrink: 0;
+ }
}
[data-component="apply-patch-file-diff"] {
- max-height: 420px;
- overflow-y: auto;
- scrollbar-width: none;
- -ms-overflow-style: none;
-
- &::-webkit-scrollbar {
- display: none;
- }
+ max-height: 420px;
+ overflow-y: auto;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
}
[data-component="tool-loaded-file"] {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 4px 0 4px 28px;
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large);
- color: var(--text-weak);
-
- [data-component="icon"] {
- flex-shrink: 0;
- color: var(--icon-weak);
- }
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 0 4px 28px;
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large);
+ color: var(--text-weak);
+
+ [data-component="icon"] {
+ flex-shrink: 0;
+ color: var(--icon-weak);
+ }
}
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 7aad01ace..9c975d549 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -49,6 +49,7 @@ import { Tooltip } from "./tooltip"
import { IconButton } from "./icon-button"
import { createAutoScroll } from "../hooks"
import { createResizeObserver } from "@solid-primitives/resize-observer"
+import { MorphChevron } from "./morph-chevron"
interface Diagnostic {
range: {
@@ -415,7 +416,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
toggleExpanded()
}}
>
- <Icon name="chevron-down" size="small" />
+ <MorphChevron expanded={expanded()} />
</button>
<div data-slot="user-message-copy-wrapper">
<Tooltip
diff --git a/packages/ui/src/components/morph-chevron.css b/packages/ui/src/components/morph-chevron.css
new file mode 100644
index 000000000..c7bab9081
--- /dev/null
+++ b/packages/ui/src/components/morph-chevron.css
@@ -0,0 +1,10 @@
+[data-slot="morph-chevron-svg"] {
+ width: 16px;
+ height: 16px;
+ display: block;
+ fill: none;
+ stroke-width: 1.5;
+ stroke: currentcolor;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+}
diff --git a/packages/ui/src/components/morph-chevron.tsx b/packages/ui/src/components/morph-chevron.tsx
new file mode 100644
index 000000000..280aeb7e3
--- /dev/null
+++ b/packages/ui/src/components/morph-chevron.tsx
@@ -0,0 +1,73 @@
+import { createEffect, createUniqueId, on } from "solid-js"
+
+export interface MorphChevronProps {
+ expanded: boolean
+ class?: string
+}
+
+const COLLAPSED = "M4 6L8 10L12 6"
+const EXPANDED = "M4 10L8 6L12 10"
+
+export function MorphChevron(props: MorphChevronProps) {
+ const id = createUniqueId()
+ let path: SVGPathElement | undefined
+ let expandAnim: SVGAnimateElement | undefined
+ let collapseAnim: SVGAnimateElement | undefined
+
+ createEffect(
+ on(
+ () => props.expanded,
+ (expanded, prev) => {
+ if (prev === undefined) {
+ // Set initial state without animation
+ path?.setAttribute("d", expanded ? EXPANDED : COLLAPSED)
+ return
+ }
+ if (expanded) {
+ expandAnim?.beginElement()
+ } else {
+ collapseAnim?.beginElement()
+ }
+ },
+ ),
+ )
+
+ return (
+ <svg
+ viewBox="0 0 16 16"
+ data-slot="morph-chevron-svg"
+ class={props.class}
+ xmlns="http://www.w3.org/2000/svg"
+ aria-hidden="true"
+ >
+ <path ref={path} d={COLLAPSED} id={`morph-chevron-path-${id}`}>
+ <animate
+ ref={(el) => {
+ expandAnim = el
+ }}
+ id={`morph-expand-${id}`}
+ attributeName="d"
+ dur="200ms"
+ fill="freeze"
+ calcMode="spline"
+ keySplines="0.25 0 0.5 1"
+ values="M4 6L8 10L12 6;M4 10L8 6L12 10"
+ begin="indefinite"
+ />
+ <animate
+ ref={(el) => {
+ collapseAnim = el
+ }}
+ id={`morph-collapse-${id}`}
+ attributeName="d"
+ dur="200ms"
+ fill="freeze"
+ calcMode="spline"
+ keySplines="0.25 0 0.5 1"
+ values="M4 10L8 6L12 10;M4 6L8 10L12 6"
+ begin="indefinite"
+ />
+ </path>
+ </svg>
+ )
+}
diff --git a/packages/ui/src/components/popover.css b/packages/ui/src/components/popover.css
index b49542afd..5a8ad10a0 100644
--- a/packages/ui/src/components/popover.css
+++ b/packages/ui/src/components/popover.css
@@ -1,98 +1,136 @@
[data-slot="popover-trigger"] {
- display: inline-flex;
+ display: inline-flex;
}
[data-component="popover-content"] {
- z-index: 50;
- min-width: 200px;
- max-width: 320px;
- border-radius: var(--radius-md);
- background-color: var(--surface-raised-stronger-non-alpha);
-
- border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
- background-clip: padding-box;
- box-shadow: var(--shadow-md);
-
- transform-origin: var(--kb-popover-content-transform-origin);
-
- &:focus-within {
- outline: none;
- }
-
- &[data-closed] {
- animation: popover-close 0.15s ease-out;
- }
-
- &[data-expanded] {
- animation: popover-open 0.15s ease-out;
- }
-
- [data-slot="popover-header"] {
- display: flex;
- padding: 12px;
- padding-bottom: 0;
- justify-content: space-between;
- align-items: center;
- gap: 8px;
-
- [data-slot="popover-title"] {
- flex: 1;
- color: var(--text-strong);
- margin: 0;
-
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- }
-
- [data-slot="popover-close-button"] {
- flex-shrink: 0;
- }
- }
-
- [data-slot="popover-description"] {
- padding: 0 12px;
- margin: 0;
- color: var(--text-base);
-
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- }
-
- [data-slot="popover-body"] {
- padding: 12px;
- }
-
- [data-slot="popover-arrow"] {
- fill: var(--surface-raised-stronger-non-alpha);
- }
+ z-index: 50;
+ min-width: 200px;
+ max-width: 320px;
+ border-radius: var(--radius-md);
+ background-color: var(--surface-raised-stronger-non-alpha);
+
+ border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
+ background-clip: padding-box;
+ box-shadow: var(--shadow-md);
+
+ transform-origin: var(--kb-popover-content-transform-origin);
+
+ animation: popoverContentHide var(--transition-duration)
+ var(--transition-easing) forwards;
+
+ @starting-style {
+ animation: none;
+ }
+
+ &[data-expanded] {
+ pointer-events: auto;
+ animation: popoverContentShow var(--transition-duration)
+ var(--transition-easing) forwards;
+ }
+
+ [data-origin-top-right] {
+ transform-origin: top right;
+ }
+
+ [data-origin-top-left] {
+ transform-origin: top left;
+ }
+
+ [data-origin-bottom-right] {
+ transform-origin: bottom right;
+ }
+
+ [data-origin-bottom-left] {
+ transform-origin: bottom left;
+ }
+
+ &:focus-within {
+ outline: none;
+ }
+
+ [data-slot="popover-header"] {
+ display: flex;
+ padding: 12px;
+ padding-bottom: 0;
+ justify-content: space-between;
+ align-items: center;
+ gap: 8px;
+
+ [data-slot="popover-title"] {
+ flex: 1;
+ color: var(--text-strong);
+ margin: 0;
+
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ [data-slot="popover-close-button"] {
+ flex-shrink: 0;
+ }
+ }
+
+ [data-slot="popover-description"] {
+ padding: 0 12px;
+ margin: 0;
+ color: var(--text-base);
+
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ [data-slot="popover-body"] {
+ padding: 12px;
+ }
+
+ [data-slot="popover-arrow"] {
+ fill: var(--surface-raised-stronger-non-alpha);
+ }
}
-@keyframes popover-open {
- from {
- opacity: 0;
- transform: scale(0.96);
- }
- to {
- opacity: 1;
- transform: scale(1);
- }
+@keyframes popoverContentShow {
+ from {
+ opacity: 0;
+ transform: scaleY(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scaleY(1);
+ }
}
-@keyframes popover-close {
- from {
- opacity: 1;
- transform: scale(1);
- }
- to {
- opacity: 0;
- transform: scale(0.96);
- }
+@keyframes popoverContentHide {
+ from {
+ opacity: 1;
+ transform: scaleY(1);
+ }
+ to {
+ opacity: 0;
+ transform: scaleY(0.95);
+ }
+}
+
+[data-component="model-popover-content"] {
+ transform-origin: var(--kb-popper-content-transform-origin);
+ pointer-events: none;
+ animation: popoverContentHide var(--transition-duration)
+ var(--transition-easing) forwards;
+
+ @starting-style {
+ animation: none;
+ }
+
+ &[data-expanded] {
+ pointer-events: auto;
+ animation: popoverContentShow var(--transition-duration)
+ var(--transition-easing) forwards;
+ }
}
diff --git a/packages/ui/src/components/progress-circle.css b/packages/ui/src/components/progress-circle.css
index afaf72af6..e06cc3070 100644
--- a/packages/ui/src/components/progress-circle.css
+++ b/packages/ui/src/components/progress-circle.css
@@ -1,12 +1,10 @@
[data-component="progress-circle"] {
- transform: rotate(-90deg);
+ color: inherit;
- [data-slot="progress-circle-background"] {
- stroke: var(--border-weak-base);
- }
-
- [data-slot="progress-circle-progress"] {
- stroke: var(--border-active);
- transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
- }
+ [data-slot="progress-circle-background"] {
+ transform-origin: 50% 50%;
+ transform: rotate(270deg);
+ stroke-opacity: 0.5;
+ transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
+ }
}
diff --git a/packages/ui/src/components/progress-circle.tsx b/packages/ui/src/components/progress-circle.tsx
index 02bd36bb7..40d1e2022 100644
--- a/packages/ui/src/components/progress-circle.tsx
+++ b/packages/ui/src/components/progress-circle.tsx
@@ -1,4 +1,4 @@
-import { type ComponentProps, createMemo, splitProps } from "solid-js"
+import { type ComponentProps, splitProps } from "solid-js"
export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList"> {
percentage: number
@@ -9,26 +9,15 @@ export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class"
export function ProgressCircle(props: ProgressCircleProps) {
const [split, rest] = splitProps(props, ["percentage", "size", "strokeWidth", "class", "classList"])
- const size = () => split.size || 16
- const strokeWidth = () => split.strokeWidth || 3
-
- const viewBoxSize = 16
- const center = viewBoxSize / 2
- const radius = () => center - strokeWidth() / 2
- const circumference = createMemo(() => 2 * Math.PI * radius())
-
- const offset = createMemo(() => {
- const clampedPercentage = Math.max(0, Math.min(100, split.percentage || 0))
- const progress = clampedPercentage / 100
- return circumference() * (1 - progress)
- })
+ const size = () => split.size || 18
+ const r = 7
return (
<svg
{...rest}
width={size()}
height={size()}
- viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
+ viewBox="0 0 18 18"
fill="none"
data-component="progress-circle"
classList={{
@@ -36,21 +25,18 @@ export function ProgressCircle(props: ProgressCircleProps) {
[split.class ?? ""]: !!split.class,
}}
>
- <circle
- cx={center}
- cy={center}
- r={radius()}
- data-slot="progress-circle-background"
- stroke-width={strokeWidth()}
- />
- <circle
- cx={center}
- cy={center}
- r={radius()}
- data-slot="progress-circle-progress"
- stroke-width={strokeWidth()}
- stroke-dasharray={circumference().toString()}
- stroke-dashoffset={offset()}
+ <circle cx="9" cy="9" r="7.75" stroke="currentColor" stroke-width="1.5" />
+ <path
+ opacity="0.5"
+ d={(() => {
+ const pct = Math.min(100, Math.max(0, split.percentage))
+ const angle = (pct / 100) * 2 * Math.PI - Math.PI / 2
+ const x = 9 + r * Math.cos(angle)
+ const y = 9 + r * Math.sin(angle)
+ const largeArc = pct > 50 ? 1 : 0
+ return `M9 2A${r} ${r} 0 ${largeArc} 1 ${x} ${y}L9 9Z`
+ })()}
+ fill="currentColor"
/>
</svg>
)
diff --git a/packages/ui/src/components/provider-icon.css b/packages/ui/src/components/provider-icon.css
index 142c5ca7c..a125315b3 100644
--- a/packages/ui/src/components/provider-icon.css
+++ b/packages/ui/src/components/provider-icon.css
@@ -1,5 +1,5 @@
[data-component="provider-icon"] {
- flex-shrink: 0;
- width: 16px;
- height: 16px;
+ flex-shrink: 0;
+ width: 16px;
+ height: 16px;
}
diff --git a/packages/ui/src/components/radio-group.css b/packages/ui/src/components/radio-group.css
index 3d672bb30..a6af7ca0f 100644
--- a/packages/ui/src/components/radio-group.css
+++ b/packages/ui/src/components/radio-group.css
@@ -1,157 +1,169 @@
[data-component="radio-group"] {
- display: flex;
- flex-direction: column;
- gap: calc(var(--spacing) * 2);
-
- [data-slot="radio-group-wrapper"] {
- all: unset;
- background-color: var(--surface-base);
- border-radius: var(--radius-md);
- box-shadow: var(--shadow-xs-border);
- margin: 0;
- padding: 0;
- position: relative;
- width: fit-content;
- }
-
- [data-slot="radio-group-items"] {
- display: inline-flex;
- list-style: none;
- flex-direction: row;
- }
-
- [data-slot="radio-group-indicator"] {
- background: var(--button-secondary-base);
- border-radius: var(--radius-md);
- box-shadow: var(--shadow-xs-border);
- content: "";
- opacity: var(--indicator-opacity, 1);
- position: absolute;
- transition:
- opacity 300ms ease-in-out,
- box-shadow 100ms ease-in-out,
- width 150ms ease,
- height 150ms ease,
- transform 150ms ease;
- }
-
- [data-slot="radio-group-item"] {
- position: relative;
- }
-
- /* Separator between items */
- [data-slot="radio-group-item"]:not(:first-of-type)::before {
- background: var(--border-weak-base);
- border-radius: var(--radius-xs);
- content: "";
- inset: 6px 0;
- position: absolute;
- transition: opacity 150ms ease;
- width: 1px;
- transform: translateX(-0.5px);
- }
-
- /* Hide separator when item or previous item is checked */
- [data-slot="radio-group-item"]:has([data-slot="radio-group-item-input"][data-checked])::before,
- [data-slot="radio-group-item"]:has([data-slot="radio-group-item-input"][data-checked])
- + [data-slot="radio-group-item"]::before {
- opacity: 0;
- }
-
- [data-slot="radio-group-item-label"] {
- color: var(--text-weak);
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-medium);
- border-radius: var(--radius-md);
- cursor: pointer;
- display: flex;
- flex-wrap: nowrap;
- gap: calc(var(--spacing) * 1);
- line-height: 1;
- padding: 6px 12px;
- place-content: center;
- position: relative;
- transition-duration: 150ms;
- transition-property: color, opacity;
- transition-timing-function: ease-in-out;
- user-select: none;
- }
-
- [data-slot="radio-group-item-input"] {
- all: unset;
- }
-
- /* Checked state */
- [data-slot="radio-group-item-input"][data-checked] + [data-slot="radio-group-item-label"] {
- color: var(--text-strong);
- }
-
- /* Disabled state */
- [data-slot="radio-group-item-input"][data-disabled] + [data-slot="radio-group-item-label"] {
- cursor: not-allowed;
- opacity: 0.5;
- }
-
- /* Hover state for unchecked, enabled items */
- [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled]) + [data-slot="radio-group-item-label"] {
- cursor: pointer;
- user-select: none;
- }
-
- [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
- + [data-slot="radio-group-item-label"]:hover {
- color: var(--text-base);
- }
-
- [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
- + [data-slot="radio-group-item-label"]:active {
- opacity: 0.7;
- }
-
- /* Focus state */
- [data-slot="radio-group-wrapper"]:has([data-slot="radio-group-item-input"]:focus-visible)
- [data-slot="radio-group-indicator"] {
- box-shadow: var(--shadow-xs-border-focus);
- }
-
- /* Hide indicator when nothing is checked */
- [data-slot="radio-group-wrapper"]:not(:has([data-slot="radio-group-item-input"][data-checked]))
- [data-slot="radio-group-indicator"] {
- --indicator-opacity: 0;
- }
-
- /* Vertical orientation */
- &[aria-orientation="vertical"] [data-slot="radio-group-items"] {
- flex-direction: column;
- }
-
- &[aria-orientation="vertical"] [data-slot="radio-group-item"]:not(:first-of-type)::before {
- height: 1px;
- width: auto;
- inset: 0 6px;
- transform: translateY(-0.5px);
- }
-
- /* Small size variant */
- &[data-size="small"] {
- [data-slot="radio-group-item-label"] {
- font-size: 12px;
- padding: 4px 8px;
- }
-
- [data-slot="radio-group-item"]:not(:first-of-type)::before {
- inset: 4px 0;
- }
-
- &[aria-orientation="vertical"] [data-slot="radio-group-item"]:not(:first-of-type)::before {
- inset: 0 4px;
- }
- }
-
- /* Disabled root state */
- &[data-disabled] {
- opacity: 0.5;
- cursor: not-allowed;
- }
+ display: flex;
+ flex-direction: column;
+ gap: calc(var(--spacing) * 2);
+
+ [data-slot="radio-group-wrapper"] {
+ all: unset;
+ background-color: var(--surface-base);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-xs-border);
+ margin: 0;
+ padding: 0;
+ position: relative;
+ width: fit-content;
+ }
+
+ [data-slot="radio-group-items"] {
+ display: inline-flex;
+ list-style: none;
+ flex-direction: row;
+ }
+
+ [data-slot="radio-group-indicator"] {
+ background: var(--button-secondary-base);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-xs-border);
+ content: "";
+ opacity: var(--indicator-opacity, 1);
+ position: absolute;
+ transition-property: opacity, box-shadow, width, height, transform;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ }
+
+ [data-slot="radio-group-item"] {
+ position: relative;
+ }
+
+ /* Separator between items */
+ [data-slot="radio-group-item"]:not(:first-of-type)::before {
+ background: var(--border-weak-base);
+ border-radius: var(--radius-xs);
+ content: "";
+ inset: 6px 0;
+ position: absolute;
+ transition-property: opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ width: 1px;
+ transform: translateX(-0.5px);
+ }
+
+ /* Hide separator when item or previous item is checked */
+ [data-slot="radio-group-item"]:has(
+ [data-slot="radio-group-item-input"][data-checked]
+ )::before,
+ [data-slot="radio-group-item"]:has(
+ [data-slot="radio-group-item-input"][data-checked]
+ )
+ + [data-slot="radio-group-item"]::before {
+ opacity: 0;
+ }
+
+ [data-slot="radio-group-item-label"] {
+ color: var(--text-weak);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-medium);
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ display: flex;
+ flex-wrap: nowrap;
+ gap: calc(var(--spacing) * 1);
+ line-height: 1;
+ padding: 6px 12px;
+ place-content: center;
+ position: relative;
+ transition-property: color, opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ user-select: none;
+ }
+
+ [data-slot="radio-group-item-input"] {
+ all: unset;
+ }
+
+ /* Checked state */
+ [data-slot="radio-group-item-input"][data-checked]
+ + [data-slot="radio-group-item-label"] {
+ color: var(--text-strong);
+ }
+
+ /* Disabled state */
+ [data-slot="radio-group-item-input"][data-disabled]
+ + [data-slot="radio-group-item-label"] {
+ cursor: not-allowed;
+ opacity: 0.5;
+ }
+
+ /* Hover state for unchecked, enabled items */
+ [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
+ + [data-slot="radio-group-item-label"] {
+ cursor: pointer;
+ user-select: none;
+ }
+
+ [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
+ + [data-slot="radio-group-item-label"]:hover {
+ color: var(--text-base);
+ }
+
+ [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
+ + [data-slot="radio-group-item-label"]:active {
+ opacity: 0.7;
+ }
+
+ /* Focus state */
+ [data-slot="radio-group-wrapper"]:has(
+ [data-slot="radio-group-item-input"]:focus-visible
+ )
+ [data-slot="radio-group-indicator"] {
+ box-shadow: var(--shadow-xs-border-focus);
+ }
+
+ /* Hide indicator when nothing is checked */
+ [data-slot="radio-group-wrapper"]:not(
+ :has([data-slot="radio-group-item-input"][data-checked])
+ )
+ [data-slot="radio-group-indicator"] {
+ --indicator-opacity: 0;
+ }
+
+ /* Vertical orientation */
+ &[aria-orientation="vertical"] [data-slot="radio-group-items"] {
+ flex-direction: column;
+ }
+
+ &[aria-orientation="vertical"]
+ [data-slot="radio-group-item"]:not(:first-of-type)::before {
+ height: 1px;
+ width: auto;
+ inset: 0 6px;
+ transform: translateY(-0.5px);
+ }
+
+ /* Small size variant */
+ &[data-size="small"] {
+ [data-slot="radio-group-item-label"] {
+ font-size: 12px;
+ padding: 4px 8px;
+ }
+
+ [data-slot="radio-group-item"]:not(:first-of-type)::before {
+ inset: 4px 0;
+ }
+
+ &[aria-orientation="vertical"]
+ [data-slot="radio-group-item"]:not(:first-of-type)::before {
+ inset: 0 4px;
+ }
+ }
+
+ /* Disabled root state */
+ &[data-disabled] {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
}
diff --git a/packages/ui/src/components/reasoning-icon.css b/packages/ui/src/components/reasoning-icon.css
new file mode 100644
index 000000000..81545cdf4
--- /dev/null
+++ b/packages/ui/src/components/reasoning-icon.css
@@ -0,0 +1,10 @@
+[data-component="reasoning-icon"] {
+ color: var(--icon-strong-base);
+
+ [data-slot="reasoning-icon-percentage"] {
+ transition: clip-path 200ms cubic-bezier(0.25, 0, 0.5, 1);
+ clip-path: inset(
+ calc(100% - var(--reasoning-icon-percentage) * 100%) 0 0 0
+ );
+ }
+}
diff --git a/packages/ui/src/components/reasoning-icon.tsx b/packages/ui/src/components/reasoning-icon.tsx
new file mode 100644
index 000000000..0ad60e964
--- /dev/null
+++ b/packages/ui/src/components/reasoning-icon.tsx
@@ -0,0 +1,32 @@
+import { type ComponentProps, splitProps } from "solid-js"
+
+export interface ReasoningIconProps extends Pick<ComponentProps<"svg">, "class" | "classList"> {
+ percentage: number
+ size?: number
+ strokeWidth?: number
+}
+
+export function ReasoningIcon(props: ReasoningIconProps) {
+ const [split, rest] = splitProps(props, ["percentage", "size", "strokeWidth", "class", "classList"])
+
+ const size = () => split.size || 16
+ const strokeWidth = () => split.strokeWidth || 1.25
+
+ return (
+ <svg
+ {...rest}
+ width={size()}
+ height={size()}
+ viewBox={`0 0 16 16`}
+ fill="none"
+ data-component="reasoning-icon"
+ classList={{
+ ...(split.classList ?? {}),
+ [split.class ?? ""]: !!split.class,
+ }}
+ >
+ <path d="M5.83196 10.3225V11.1666C5.83196 11.7189 6.27967 12.1666 6.83196 12.1666H9.16687C9.71915 12.1666 10.1669 11.7189 10.1669 11.1666V10.3225M5.83196 10.3225C5.55695 10.1843 5.29695 10.0206 5.05505 9.83459C3.90601 8.95086 3.16549 7.56219 3.16549 6.00055C3.16549 3.33085 5.32971 1.16663 7.99941 1.16663C10.6691 1.16663 12.8333 3.33085 12.8333 6.00055C12.8333 7.56219 12.0928 8.95086 10.9438 9.83459C10.7019 10.0206 10.4419 10.1843 10.1669 10.3225M5.83196 10.3225H10.1669M6.5 14.1666H9.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" />
+ <circle cx="8" cy="5.83325" r="2.86364" fill="currentColor" stroke="currentColor" stroke-width={strokeWidth()} style={{ '--reasoning-icon-percentage': split.percentage / 100 }} data-slot="reasoning-icon-percentage" />
+ </svg>
+ )
+}
diff --git a/packages/ui/src/components/resize-handle.css b/packages/ui/src/components/resize-handle.css
index c309ff838..5c26b490f 100644
--- a/packages/ui/src/components/resize-handle.css
+++ b/packages/ui/src/components/resize-handle.css
@@ -1,58 +1,60 @@
[data-component="resize-handle"] {
- position: absolute;
- z-index: 10;
-
- &::after {
- content: "";
- position: absolute;
- opacity: 0;
- transition: opacity 0.15s ease-in-out;
- }
-
- &:hover::after,
- &:active::after {
- opacity: 1;
- }
-
- &[data-direction="horizontal"] {
- inset-block: 0;
- inset-inline-end: 0;
- width: 8px;
- transform: translateX(50%);
- cursor: col-resize;
-
- &[data-edge="start"] {
- inset-inline-start: 0;
- inset-inline-end: auto;
- transform: translateX(-50%);
- }
-
- &::after {
- width: 3px;
- inset-block: 0;
- inset-inline-start: 50%;
- transform: translateX(-50%);
- }
- }
-
- &[data-direction="vertical"] {
- inset-inline: 0;
- inset-block-start: 0;
- height: 8px;
- transform: translateY(-50%);
- cursor: row-resize;
-
- &[data-edge="end"] {
- inset-block-start: auto;
- inset-block-end: 0;
- transform: translateY(50%);
- }
-
- &::after {
- height: 3px;
- inset-inline: 0;
- inset-block-start: 50%;
- transform: translateY(-50%);
- }
- }
+ position: absolute;
+ z-index: 10;
+
+ &::after {
+ content: "";
+ position: absolute;
+ opacity: 0;
+ transition-property: opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ }
+
+ &:hover::after,
+ &:active::after {
+ opacity: 1;
+ }
+
+ &[data-direction="horizontal"] {
+ inset-block: 0;
+ inset-inline-end: 0;
+ width: 8px;
+ transform: translateX(50%);
+ cursor: col-resize;
+
+ &[data-edge="start"] {
+ inset-inline-start: 0;
+ inset-inline-end: auto;
+ transform: translateX(-50%);
+ }
+
+ &::after {
+ width: 3px;
+ inset-block: 0;
+ inset-inline-start: 50%;
+ transform: translateX(-50%);
+ }
+ }
+
+ &[data-direction="vertical"] {
+ inset-inline: 0;
+ inset-block-start: 0;
+ height: 8px;
+ transform: translateY(-50%);
+ cursor: row-resize;
+
+ &[data-edge="end"] {
+ inset-block-start: auto;
+ inset-block-end: 0;
+ transform: translateY(50%);
+ }
+
+ &::after {
+ height: 3px;
+ inset-inline: 0;
+ inset-block-start: 50%;
+ transform: translateY(-50%);
+ }
+ }
}
diff --git a/packages/ui/src/components/scroll-fade.css b/packages/ui/src/components/scroll-fade.css
new file mode 100644
index 000000000..1e6b30d27
--- /dev/null
+++ b/packages/ui/src/components/scroll-fade.css
@@ -0,0 +1,122 @@
+[data-component="scroll-fade"] {
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: none;
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ -ms-overflow-style: none;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ &[data-direction="horizontal"] {
+ overflow-x: auto;
+ overflow-y: hidden;
+
+ /* Both fades */
+ &[data-fade-start][data-fade-end] {
+ mask-image: linear-gradient(
+ to right,
+ transparent,
+ black var(--scroll-fade-start),
+ black calc(100% - var(--scroll-fade-end)),
+ transparent
+ );
+ -webkit-mask-image: linear-gradient(
+ to right,
+ transparent,
+ black var(--scroll-fade-start),
+ black calc(100% - var(--scroll-fade-end)),
+ transparent
+ );
+ }
+
+ /* Only start fade */
+ &[data-fade-start]:not([data-fade-end]) {
+ mask-image: linear-gradient(
+ to right,
+ transparent,
+ black var(--scroll-fade-start),
+ black 100%
+ );
+ -webkit-mask-image: linear-gradient(
+ to right,
+ transparent,
+ black var(--scroll-fade-start),
+ black 100%
+ );
+ }
+
+ /* Only end fade */
+ &:not([data-fade-start])[data-fade-end] {
+ mask-image: linear-gradient(
+ to right,
+ black 0%,
+ black calc(100% - var(--scroll-fade-end)),
+ transparent
+ );
+ -webkit-mask-image: linear-gradient(
+ to right,
+ black 0%,
+ black calc(100% - var(--scroll-fade-end)),
+ transparent
+ );
+ }
+ }
+
+ &[data-direction="vertical"] {
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ &[data-fade-start][data-fade-end] {
+ mask-image: linear-gradient(
+ to bottom,
+ transparent,
+ black var(--scroll-fade-start),
+ black calc(100% - var(--scroll-fade-end)),
+ transparent
+ );
+ -webkit-mask-image: linear-gradient(
+ to bottom,
+ transparent,
+ black var(--scroll-fade-start),
+ black calc(100% - var(--scroll-fade-end)),
+ transparent
+ );
+ }
+
+ /* Only start fade */
+ &[data-fade-start]:not([data-fade-end]) {
+ mask-image: linear-gradient(
+ to bottom,
+ transparent,
+ black var(--scroll-fade-start),
+ black 100%
+ );
+ -webkit-mask-image: linear-gradient(
+ to bottom,
+ transparent,
+ black var(--scroll-fade-start),
+ black 100%
+ );
+ }
+
+ /* Only end fade */
+ &:not([data-fade-start])[data-fade-end] {
+ mask-image: linear-gradient(
+ to bottom,
+ black 0%,
+ black calc(100% - var(--scroll-fade-end)),
+ transparent
+ );
+ -webkit-mask-image: linear-gradient(
+ to bottom,
+ black 0%,
+ black calc(100% - var(--scroll-fade-end)),
+ transparent
+ );
+ }
+ }
+}
diff --git a/packages/ui/src/components/scroll-fade.tsx b/packages/ui/src/components/scroll-fade.tsx
new file mode 100644
index 000000000..0effb678a
--- /dev/null
+++ b/packages/ui/src/components/scroll-fade.tsx
@@ -0,0 +1,207 @@
+import { type JSX, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
+import "./scroll-fade.css"
+
+export interface ScrollFadeProps extends JSX.HTMLAttributes<HTMLDivElement> {
+ direction?: "horizontal" | "vertical"
+ fadeStartSize?: number
+ fadeEndSize?: number
+ trackTransformSelector?: string
+ ref?: (el: HTMLDivElement) => void
+}
+
+export function ScrollFade(props: ScrollFadeProps) {
+ const [local, others] = splitProps(props, [
+ "children",
+ "direction",
+ "fadeStartSize",
+ "fadeEndSize",
+ "trackTransformSelector",
+ "class",
+ "style",
+ "ref",
+ ])
+
+ const direction = () => local.direction ?? "vertical"
+ const fadeStartSize = () => local.fadeStartSize ?? 20
+ const fadeEndSize = () => local.fadeEndSize ?? 20
+
+ const getTransformOffset = (element: Element): number => {
+ const style = getComputedStyle(element)
+ const transform = style.transform
+ if (!transform || transform === "none") return 0
+
+ const match = transform.match(/matrix(?:3d)?\(([^)]+)\)/)
+ if (!match) return 0
+
+ const values = match[1].split(",").map((v) => parseFloat(v.trim()))
+ const isHorizontal = direction() === "horizontal"
+
+ if (transform.startsWith("matrix3d")) {
+ return isHorizontal ? -(values[12] || 0) : -(values[13] || 0)
+ } else {
+ return isHorizontal ? -(values[4] || 0) : -(values[5] || 0)
+ }
+ }
+
+ let containerRef: HTMLDivElement | undefined
+
+ const [fadeStart, setFadeStart] = createSignal(0)
+ const [fadeEnd, setFadeEnd] = createSignal(0)
+ const [isScrollable, setIsScrollable] = createSignal(false)
+
+ let lastScrollPos = 0
+ let lastTransformPos = 0
+ let lastScrollSize = 0
+ let lastClientSize = 0
+
+ const updateFade = () => {
+ if (!containerRef) return
+
+ const isHorizontal = direction() === "horizontal"
+ const scrollPos = isHorizontal ? containerRef.scrollLeft : containerRef.scrollTop
+ const scrollSize = isHorizontal ? containerRef.scrollWidth : containerRef.scrollHeight
+ const clientSize = isHorizontal ? containerRef.clientWidth : containerRef.clientHeight
+
+ let transformPos = 0
+ if (local.trackTransformSelector) {
+ const transformElement = containerRef.querySelector(local.trackTransformSelector)
+ if (transformElement) {
+ transformPos = getTransformOffset(transformElement)
+ }
+ }
+
+ const effectiveScrollPos = Math.max(scrollPos, transformPos)
+
+ if (
+ effectiveScrollPos === lastScrollPos &&
+ transformPos === lastTransformPos &&
+ scrollSize === lastScrollSize &&
+ clientSize === lastClientSize
+ ) {
+ return
+ }
+
+ lastScrollPos = effectiveScrollPos
+ lastTransformPos = transformPos
+ lastScrollSize = scrollSize
+ lastClientSize = clientSize
+
+ const maxScroll = scrollSize - clientSize
+ const canScroll = maxScroll > 1
+
+ setIsScrollable(canScroll)
+
+ if (!canScroll) {
+ setFadeStart(0)
+ setFadeEnd(0)
+ return
+ }
+
+ const progress = maxScroll > 0 ? effectiveScrollPos / maxScroll : 0
+
+ const startProgress = Math.min(progress / 0.1, 1)
+ setFadeStart(startProgress * fadeStartSize())
+
+ const endProgress = progress > 0.9 ? (1 - progress) / 0.1 : 1
+ setFadeEnd(Math.max(0, endProgress) * fadeEndSize())
+ }
+
+ onMount(() => {
+ if (!containerRef) return
+
+ updateFade()
+
+ let rafId: number | undefined
+ let isPolling = false
+ let pollTimeout: ReturnType<typeof setTimeout> | undefined
+
+ const startPolling = () => {
+ if (isPolling) return
+ isPolling = true
+
+ const pollScroll = () => {
+ updateFade()
+ rafId = requestAnimationFrame(pollScroll)
+ }
+ rafId = requestAnimationFrame(pollScroll)
+ }
+
+ const stopPolling = () => {
+ if (!isPolling) return
+ isPolling = false
+ if (rafId !== undefined) {
+ cancelAnimationFrame(rafId)
+ rafId = undefined
+ }
+ }
+
+ const schedulePollingStop = () => {
+ if (pollTimeout !== undefined) clearTimeout(pollTimeout)
+ pollTimeout = setTimeout(stopPolling, 1000)
+ }
+
+ const onActivity = () => {
+ updateFade()
+ if (local.trackTransformSelector) {
+ startPolling()
+ schedulePollingStop()
+ }
+ }
+
+ containerRef.addEventListener("scroll", onActivity, { passive: true })
+
+ const resizeObserver = new ResizeObserver(() => {
+ lastScrollSize = 0
+ lastClientSize = 0
+ onActivity()
+ })
+ resizeObserver.observe(containerRef)
+
+ const mutationObserver = new MutationObserver(() => {
+ lastScrollSize = 0
+ lastClientSize = 0
+ requestAnimationFrame(onActivity)
+ })
+ mutationObserver.observe(containerRef, {
+ childList: true,
+ subtree: true,
+ characterData: true,
+ })
+
+ onCleanup(() => {
+ containerRef?.removeEventListener("scroll", onActivity)
+ resizeObserver.disconnect()
+ mutationObserver.disconnect()
+ stopPolling()
+ if (pollTimeout !== undefined) clearTimeout(pollTimeout)
+ })
+ })
+
+ createEffect(() => {
+ local.children
+ requestAnimationFrame(updateFade)
+ })
+
+ return (
+ <div
+ ref={(el) => {
+ containerRef = el
+ local.ref?.(el)
+ }}
+ data-component="scroll-fade"
+ data-direction={direction()}
+ data-scrollable={isScrollable() || undefined}
+ data-fade-start={fadeStart() > 0 || undefined}
+ data-fade-end={fadeEnd() > 0 || undefined}
+ class={local.class}
+ style={{
+ ...(typeof local.style === "object" ? local.style : {}),
+ "--scroll-fade-start": `${fadeStart()}px`,
+ "--scroll-fade-end": `${fadeEnd()}px`,
+ }}
+ {...others}
+ >
+ {local.children}
+ </div>
+ )
+}
diff --git a/packages/ui/src/components/scroll-reveal.tsx b/packages/ui/src/components/scroll-reveal.tsx
new file mode 100644
index 000000000..3e9c8b362
--- /dev/null
+++ b/packages/ui/src/components/scroll-reveal.tsx
@@ -0,0 +1,141 @@
+import { type JSX, onCleanup, splitProps } from "solid-js"
+import { ScrollFade, type ScrollFadeProps } from './scroll-fade'
+
+const SCROLL_SPEED = 60
+const PAUSE_DURATION = 800
+
+type ScrollAnimationState = {
+ rafId: number | null
+ startTime: number
+ running: boolean
+}
+
+const startScrollAnimation = (containerEl: HTMLElement): ScrollAnimationState | null => {
+ containerEl.offsetHeight
+
+ const extraWidth = containerEl.scrollWidth - containerEl.clientWidth
+
+ if (extraWidth <= 0) {
+ return null
+ }
+
+ const scrollDuration = (extraWidth / SCROLL_SPEED) * 1000
+ const totalDuration = PAUSE_DURATION + scrollDuration + PAUSE_DURATION + scrollDuration + PAUSE_DURATION
+
+ const state: ScrollAnimationState = {
+ rafId: null,
+ startTime: performance.now(),
+ running: true,
+ }
+
+ const animate = (currentTime: number) => {
+ if (!state.running) return
+
+ const elapsed = currentTime - state.startTime
+ const progress = (elapsed % totalDuration) / totalDuration
+
+ const pausePercent = PAUSE_DURATION / totalDuration
+ const scrollPercent = scrollDuration / totalDuration
+
+ const pauseEnd1 = pausePercent
+ const scrollEnd1 = pauseEnd1 + scrollPercent
+ const pauseEnd2 = scrollEnd1 + pausePercent
+ const scrollEnd2 = pauseEnd2 + scrollPercent
+
+ let scrollPos = 0
+
+ if (progress < pauseEnd1) {
+ scrollPos = 0
+ } else if (progress < scrollEnd1) {
+ const scrollProgress = (progress - pauseEnd1) / scrollPercent
+ scrollPos = scrollProgress * extraWidth
+ } else if (progress < pauseEnd2) {
+ scrollPos = extraWidth
+ } else if (progress < scrollEnd2) {
+ const scrollProgress = (progress - pauseEnd2) / scrollPercent
+ scrollPos = extraWidth * (1 - scrollProgress)
+ } else {
+ scrollPos = 0
+ }
+
+ containerEl.scrollLeft = scrollPos
+ state.rafId = requestAnimationFrame(animate)
+ }
+
+ state.rafId = requestAnimationFrame(animate)
+ return state
+}
+
+const stopScrollAnimation = (state: ScrollAnimationState | null, containerEl?: HTMLElement) => {
+ if (state) {
+ state.running = false
+ if (state.rafId !== null) {
+ cancelAnimationFrame(state.rafId)
+ }
+ }
+ if (containerEl) {
+ containerEl.scrollLeft = 0
+ }
+}
+
+export interface ScrollRevealProps extends Omit<ScrollFadeProps, "direction"> {
+ hoverDelay?: number
+}
+
+export function ScrollReveal(props: ScrollRevealProps) {
+ const [local, others] = splitProps(props, ["children", "hoverDelay", "ref"])
+
+ const hoverDelay = () => local.hoverDelay ?? 300
+
+ let containerRef: HTMLDivElement | undefined
+ let hoverTimeout: ReturnType<typeof setTimeout> | undefined
+ let scrollAnimationState: ScrollAnimationState | null = null
+
+ const handleMouseEnter: JSX.EventHandler<HTMLDivElement, MouseEvent> = () => {
+ hoverTimeout = setTimeout(() => {
+ if (!containerRef) return
+
+ containerRef.offsetHeight
+
+ const isScrollable = containerRef.scrollWidth > containerRef.clientWidth + 1
+
+ if (isScrollable) {
+ stopScrollAnimation(scrollAnimationState, containerRef)
+ scrollAnimationState = startScrollAnimation(containerRef)
+ }
+ }, hoverDelay())
+ }
+
+ const handleMouseLeave: JSX.EventHandler<HTMLDivElement, MouseEvent> = () => {
+ if (hoverTimeout) {
+ clearTimeout(hoverTimeout)
+ hoverTimeout = undefined
+ }
+ stopScrollAnimation(scrollAnimationState, containerRef)
+ scrollAnimationState = null
+ }
+
+ onCleanup(() => {
+ if (hoverTimeout) {
+ clearTimeout(hoverTimeout)
+ }
+ stopScrollAnimation(scrollAnimationState, containerRef)
+ })
+
+ return (
+ <ScrollFade
+ ref={(el) => {
+ containerRef = el
+ local.ref?.(el)
+ }}
+ fadeStartSize={8}
+ fadeEndSize={8}
+ direction="horizontal"
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ {...others}
+ >
+ {local.children}
+ </ScrollFade>
+ )
+}
diff --git a/packages/ui/src/components/select.css b/packages/ui/src/components/select.css
index 25dd2eb40..aafe421aa 100644
--- a/packages/ui/src/components/select.css
+++ b/packages/ui/src/components/select.css
@@ -1,202 +1,165 @@
[data-component="select"] {
- [data-slot="select-select-trigger"] {
- padding: 0 4px 0 8px;
- box-shadow: none;
-
- [data-slot="select-select-trigger-value"] {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- [data-slot="select-select-trigger-icon"] {
- width: 16px;
- height: 16px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- color: var(--text-weak);
- transition: transform 0.1s ease-in-out;
- }
-
- &[data-expanded] {
- &[data-variant="secondary"] {
- background-color: var(--button-secondary-hover);
- }
- &[data-variant="ghost"] {
- background-color: var(--surface-raised-base-active);
- }
- &[data-variant="primary"] {
- background-color: var(--icon-strong-active);
- }
- }
-
- &:not([data-expanded]):focus-visible {
- &[data-variant="secondary"] {
- background-color: var(--button-secondary-base);
- }
- &[data-variant="ghost"] {
- background-color: var(--surface-raised-base-hover);
- }
- &[data-variant="primary"] {
- background-color: var(--icon-strong-base);
- }
- }
- }
-
- &[data-trigger-style="settings"] {
- [data-slot="select-select-trigger"] {
- padding: 6px 6px 6px 12px;
- box-shadow: none;
- border-radius: 6px;
- min-width: 160px;
- height: 32px;
- justify-content: flex-end;
- gap: 12px;
- background-color: transparent;
-
- [data-slot="select-select-trigger-value"] {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- font-size: var(--font-size-base);
- font-weight: var(--font-weight-regular);
- }
- [data-slot="select-select-trigger-icon"] {
- width: 16px;
- height: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- color: var(--text-weak);
- background-color: var(--surface-raised-base);
- border-radius: 4px;
- transition: transform 0.1s ease-in-out;
- }
-
- &[data-slot="select-select-trigger"]:hover:not(:disabled),
- &[data-slot="select-select-trigger"][data-expanded],
- &[data-slot="select-select-trigger"][data-expanded]:hover:not(:disabled) {
- background-color: var(--input-base);
- box-shadow: var(--shadow-xs-border-base);
- }
-
- &:not([data-expanded]):focus {
- background-color: transparent;
- box-shadow: none;
- }
- }
- }
+ [data-slot="select-select-trigger"] {
+ display: flex;
+ padding: 4px 8px !important;
+ align-items: center;
+ justify-content: space-between;
+ box-shadow: none;
+ transition-property: background-color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+
+ [data-slot="select-select-trigger-value"] {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ [data-slot="select-select-trigger-icon"] {
+ width: 16px;
+ height: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ color: var(--icon-base);
+ }
+
+ &:hover,
+ &[data-expanded] {
+ &[data-variant="secondary"] {
+ background-color: var(--button-secondary-hover);
+ }
+ &[data-variant="ghost"] {
+ background-color: var(--surface-raised-base-active);
+ }
+ &[data-variant="primary"] {
+ background-color: var(--icon-strong-active);
+ }
+ }
+ &:not([data-expanded]):focus,
+ &:not([data-expanded]):focus-visible {
+ &[data-variant="secondary"] {
+ background-color: var(--button-secondary-base);
+ }
+ &[data-variant="ghost"] {
+ background-color: transparent;
+ }
+ &[data-variant="primary"] {
+ background-color: var(--icon-strong-base);
+ }
+ }
+ }
}
[data-component="select-content"] {
- min-width: 104px;
- max-width: 23rem;
- overflow: hidden;
- border-radius: var(--radius-md);
- background-color: var(--surface-raised-stronger-non-alpha);
- padding: 4px;
- box-shadow: var(--shadow-xs-border);
- z-index: 60;
-
- &[data-expanded] {
- animation: select-open 0.15s ease-out;
- }
-
- [data-slot="select-select-content-list"] {
- overflow-y: auto;
- max-height: 12rem;
- white-space: nowrap;
- overflow-x: hidden;
- display: flex;
- flex-direction: column;
-
- &:focus {
- outline: none;
- }
-
- > *:not([role="presentation"]) + *:not([role="presentation"]) {
- margin-top: 2px;
- }
- }
-
- [data-slot="select-select-item"] {
- position: relative;
- display: flex;
- align-items: center;
- padding: 2px 8px;
- gap: 12px;
- border-radius: 4px;
- cursor: default;
-
- /* 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);
-
- color: var(--text-strong);
-
- transition:
- background-color 0.2s ease-in-out,
- color 0.2s ease-in-out;
- outline: none;
- user-select: none;
-
- &[data-highlighted] {
- background: var(--surface-raised-base-hover);
- }
- &[data-disabled] {
- background-color: var(--surface-raised-base);
- pointer-events: none;
- }
- [data-slot="select-select-item-indicator"] {
- display: flex;
- align-items: center;
- justify-content: center;
- margin-left: auto;
- width: 16px;
- height: 16px;
- }
- &:focus {
- outline: none;
- }
- &:hover {
- background: var(--surface-raised-base-hover);
- }
- }
+ min-width: 8rem;
+ max-width: 23rem;
+ overflow: hidden;
+ border-radius: var(--radius-md);
+ background-color: var(--surface-raised-stronger-non-alpha);
+ padding: 4px;
+ box-shadow: var(--shadow-xs-border);
+ z-index: 50;
+ transform-origin: var(--kb-popper-content-transform-origin);
+ pointer-events: none;
+
+ animation: selectContentHide var(--transition-duration)
+ var(--transition-easing) forwards;
+
+ @starting-style {
+ animation: none;
+ }
+
+ &[data-expanded] {
+ pointer-events: auto;
+ animation: selectContentShow var(--transition-duration)
+ var(--transition-easing) forwards;
+ }
+
+ [data-slot="select-select-content-list"] {
+ overflow-y: auto;
+ max-height: 12rem;
+ white-space: nowrap;
+ overflow-x: hidden;
+ display: flex;
+ flex-direction: column;
+ &:focus {
+ outline: none;
+ }
+ > *:not([role="presentation"]) + *:not([role="presentation"]) {
+ margin-top: 2px;
+ }
+ }
+ [data-slot="select-select-item"] {
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: 4px 8px;
+ gap: 12px;
+ border-radius: var(--radius-sm);
+
+ /* text-12-medium */
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large); /* 166.667% */
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-strong);
+
+ transition-property: background-color, color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ outline: none;
+ user-select: none;
+
+ &:hover {
+ background-color: var(--surface-raised-base-hover);
+ }
+ &[data-disabled] {
+ background-color: var(--surface-raised-base);
+ pointer-events: none;
+ }
+ [data-slot="select-select-item-indicator"] {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: auto;
+ width: 16px;
+ height: 16px;
+ color: var(--icon-strong-base);
+
+ svg {
+ color: var(--icon-strong-base);
+ }
+ }
+ &:focus {
+ outline: none;
+ }
+ &:hover {
+ background: var(--surface-raised-base-hover);
+ }
+ }
}
-[data-component="select-content"][data-trigger-style="settings"] {
- min-width: 160px;
- border-radius: 8px;
- padding: 0;
-
- [data-slot="select-select-content-list"] {
- padding: 4px;
- }
-
- [data-slot="select-select-item"] {
- /* text-14-regular */
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- }
+@keyframes selectContentShow {
+ from {
+ opacity: 0;
+ transform: scaleY(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scaleY(1);
+ }
}
-@keyframes select-open {
- from {
- opacity: 0;
- transform: scale(0.95);
- }
- to {
- opacity: 1;
- transform: scale(1);
- }
+@keyframes selectContentHide {
+ from {
+ opacity: 1;
+ transform: scaleY(1);
+ }
+ to {
+ opacity: 0;
+ transform: scaleY(0.95);
+ }
}
diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx
index 0386c329e..fef00500a 100644
--- a/packages/ui/src/components/select.tsx
+++ b/packages/ui/src/components/select.tsx
@@ -1,8 +1,10 @@
import { Select as Kobalte } from "@kobalte/core/select"
-import { createMemo, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js"
+import { createMemo, createSignal, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js"
import { pipe, groupBy, entries, map } from "remeda"
+import { Show } from "solid-js"
import { Button, ButtonProps } from "./button"
import { Icon } from "./icon"
+import { MorphChevron } from "./morph-chevron"
export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "onSelect" | "children"> & {
placeholder?: string
@@ -38,6 +40,8 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
"triggerVariant",
])
+ const [isOpen, setIsOpen] = createSignal(false)
+
const state = {
key: undefined as string | undefined,
cleanup: undefined as (() => void) | void,
@@ -85,7 +89,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
data-component="select"
data-trigger-style={local.triggerVariant}
placement={local.triggerVariant === "settings" ? "bottom-end" : "bottom-start"}
- gutter={4}
+ gutter={8}
value={local.current}
options={grouped()}
optionValue={(x) => (local.value ? local.value(x) : (x as string))}
@@ -115,7 +119,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
: (itemProps.item.rawValue as string)}
</Kobalte.ItemLabel>
<Kobalte.ItemIndicator data-slot="select-select-item-indicator">
- <Icon name="check-small" size="small" />
+ <Icon name="check" size="small" />
</Kobalte.ItemIndicator>
</Kobalte.Item>
)}
@@ -124,6 +128,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
stop()
}}
onOpenChange={(open) => {
+ setIsOpen(open)
local.onOpenChange?.(open)
if (!open) stop()
}}
@@ -149,7 +154,12 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
}}
</Kobalte.Value>
<Kobalte.Icon data-slot="select-select-trigger-icon">
- <Icon name={local.triggerVariant === "settings" ? "selector" : "chevron-down"} size="small" />
+ <Show when={local.triggerVariant === "settings"}>
+ <Icon name="selector" size="small" />
+ </Show>
+ <Show when={local.triggerVariant !== "settings"}>
+ <MorphChevron expanded={isOpen()} />
+ </Show>
</Kobalte.Icon>
</Kobalte.Trigger>
<Kobalte.Portal>
diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css
index 20d2fef15..d2ec356f4 100644
--- a/packages/ui/src/components/session-review.css
+++ b/packages/ui/src/components/session-review.css
@@ -1,217 +1,216 @@
[data-component="session-review"] {
- display: flex;
- flex-direction: column;
- gap: 8px;
- height: 100%;
- overflow-y: auto;
- scrollbar-width: none;
- contain: strict;
- &::-webkit-scrollbar {
- display: none;
- }
-
- /* [data-slot="session-review-container"] { */
- /* height: 100%; */
- /* } */
-
- [data-slot="session-review-header"] {
- position: sticky;
- top: 0;
- z-index: 20;
- background-color: var(--background-stronger);
- height: 32px;
- flex-shrink: 0;
- display: flex;
- justify-content: space-between;
- align-items: center;
- align-self: stretch;
- }
-
- [data-slot="session-review-title"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-large);
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large);
- color: var(--text-strong);
- }
-
- [data-slot="session-review-actions"] {
- display: flex;
- align-items: center;
- column-gap: 16px;
- padding-right: 1px;
- }
-
- [data-component="sticky-accordion-header"] {
- top: 40px;
-
- &[data-expanded]::before {
- top: -40px;
- }
- }
-
- [data-slot="accordion-trigger"] {
- background-color: var(--background-stronger) !important;
- }
-
- [data-slot="session-review-accordion-item"][data-selected] {
- [data-slot="session-review-accordion-content"] {
- box-shadow: var(--shadow-xs-border-select);
- border-radius: var(--radius-lg);
- }
- }
-
- [data-slot="accordion-item"] {
- [data-slot="accordion-content"] {
- display: none;
- }
- &[data-expanded] {
- [data-slot="accordion-content"] {
- display: block;
- }
- }
- }
-
- [data-slot="accordion-content"] {
- -webkit-user-select: text;
- user-select: text;
- }
-
- [data-slot="session-review-accordion-content"] {
- position: relative;
- overflow: hidden;
- }
-
- [data-slot="session-review-trigger-content"] {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- gap: 20px;
- }
-
- [data-slot="session-review-file-info"] {
- flex-grow: 1;
- display: flex;
- align-items: center;
- gap: 20px;
- min-width: 0;
- }
-
- [data-slot="session-review-file-name-container"] {
- display: flex;
- flex-grow: 1;
- min-width: 0;
- }
-
- [data-slot="session-review-directory"] {
- color: var(--text-base);
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- direction: rtl;
- text-align: left;
- }
-
- [data-slot="session-review-filename"] {
- color: var(--text-strong);
- flex-shrink: 0;
- }
-
- [data-slot="session-review-view-button"] {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 2px;
- margin-left: 8px;
- border: none;
- background: transparent;
- color: var(--text-base);
- cursor: pointer;
- border-radius: 4px;
- opacity: 0;
- transition: opacity 0.15s ease;
-
- &:hover {
- color: var(--text-strong);
- background: var(--surface-base);
- }
- }
-
- [data-slot="accordion-trigger"]:hover [data-slot="session-review-view-button"] {
- opacity: 1;
- }
-
- [data-slot="session-review-trigger-actions"] {
- flex-shrink: 0;
- display: flex;
- gap: 16px;
- align-items: center;
- justify-content: flex-end;
- }
-
- [data-slot="session-review-change"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-medium);
- }
-
- [data-slot="session-review-change"][data-type="added"] {
- color: var(--icon-diff-add-base);
- }
-
- [data-slot="session-review-change"][data-type="removed"] {
- color: var(--icon-diff-delete-base);
- }
-
- [data-slot="session-review-file-container"] {
- padding: 0;
- }
-
- [data-slot="session-review-image-container"] {
- padding: 12px;
- display: flex;
- justify-content: center;
- background: var(--background-stronger);
- }
-
- [data-slot="session-review-image"] {
- max-width: 100%;
- max-height: 60vh;
- object-fit: contain;
- border-radius: 8px;
- border: 1px solid var(--border-weak-base);
- background: var(--background-base);
- }
-
- [data-slot="session-review-image-placeholder"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- color: var(--text-weak);
- }
-
- [data-slot="session-review-audio-container"] {
- padding: 12px;
- display: flex;
- justify-content: center;
- background: var(--background-stronger);
- }
-
- [data-slot="session-review-audio"] {
- width: 100%;
- max-width: 560px;
- }
-
- [data-slot="session-review-audio-placeholder"] {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- color: var(--text-weak);
- }
-
- [data-slot="session-review-diff-wrapper"] {
- position: relative;
- overflow: hidden;
- --line-comment-z: 5;
- --line-comment-popover-z: 30;
- }
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ height: 100%;
+ overflow-y: auto;
+ scrollbar-width: none;
+ contain: strict;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ /* [data-slot="session-review-container"] { */
+ /* height: 100%; */
+ /* } */
+
+ [data-slot="session-review-header"] {
+ position: sticky;
+ top: 0;
+ z-index: 20;
+ background-color: var(--background-stronger);
+ height: 32px;
+ flex-shrink: 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ align-self: stretch;
+ }
+
+ [data-slot="session-review-title"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-large);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ color: var(--text-strong);
+ }
+
+ [data-slot="session-review-actions"] {
+ display: flex;
+ align-items: center;
+ column-gap: 16px;
+ padding-right: 1px;
+ }
+
+ [data-component="sticky-accordion-header"] {
+ top: 40px;
+
+ &[data-expanded]::before {
+ top: -40px;
+ }
+ }
+
+ [data-slot="accordion-trigger"] {
+ background-color: var(--background-stronger) !important;
+ }
+
+ [data-slot="session-review-accordion-item"][data-selected] {
+ [data-slot="session-review-accordion-content"] {
+ box-shadow: var(--shadow-xs-border-select);
+ border-radius: var(--radius-lg);
+ }
+ }
+
+ [data-slot="accordion-item"] {
+ [data-slot="accordion-content"] {
+ /* Use grid-template-rows for smooth height transition */
+ display: grid;
+ }
+ }
+
+ [data-slot="accordion-content"] {
+ -webkit-user-select: text;
+ user-select: text;
+ }
+
+ [data-slot="session-review-accordion-content"] {
+ position: relative;
+ overflow: hidden;
+ }
+
+ [data-slot="session-review-trigger-content"] {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ gap: 20px;
+ }
+
+ [data-slot="session-review-file-info"] {
+ flex-grow: 1;
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ min-width: 0;
+ }
+
+ [data-slot="session-review-file-name-container"] {
+ display: flex;
+ flex-grow: 1;
+ min-width: 0;
+ }
+
+ [data-slot="session-review-directory"] {
+ color: var(--text-base);
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ direction: rtl;
+ text-align: left;
+ }
+
+ [data-slot="session-review-filename"] {
+ color: var(--text-strong);
+ flex-shrink: 0;
+ }
+
+ [data-slot="session-review-view-button"] {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2px;
+ margin-left: 8px;
+ border: none;
+ background: transparent;
+ color: var(--text-base);
+ cursor: pointer;
+ border-radius: 4px;
+ opacity: 0;
+ transition-property: opacity, background-color, color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+
+ &:hover {
+ color: var(--text-strong);
+ background: var(--surface-base);
+ }
+ }
+
+ [data-slot="accordion-trigger"]:hover
+ [data-slot="session-review-view-button"] {
+ opacity: 1;
+ }
+
+ [data-slot="session-review-trigger-actions"] {
+ flex-shrink: 0;
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ justify-content: flex-end;
+ }
+
+ [data-slot="session-review-change"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-medium);
+ }
+
+ [data-slot="session-review-change"][data-type="added"] {
+ color: var(--icon-diff-add-base);
+ }
+
+ [data-slot="session-review-change"][data-type="removed"] {
+ color: var(--icon-diff-delete-base);
+ }
+
+ [data-slot="session-review-file-container"] {
+ padding: 0;
+ }
+
+ [data-slot="session-review-image-container"] {
+ padding: 12px;
+ display: flex;
+ justify-content: center;
+ background: var(--background-stronger);
+ }
+
+ [data-slot="session-review-image"] {
+ max-width: 100%;
+ max-height: 60vh;
+ object-fit: contain;
+ border-radius: 8px;
+ border: 1px solid var(--border-weak-base);
+ background: var(--background-base);
+ }
+
+ [data-slot="session-review-image-placeholder"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ color: var(--text-weak);
+ }
+
+ [data-slot="session-review-audio-container"] {
+ padding: 12px;
+ display: flex;
+ justify-content: center;
+ background: var(--background-stronger);
+ }
+
+ [data-slot="session-review-audio"] {
+ width: 100%;
+ max-width: 560px;
+ }
+
+ [data-slot="session-review-audio-placeholder"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ color: var(--text-weak);
+ }
+
+ [data-slot="session-review-diff-wrapper"] {
+ position: relative;
+ overflow: hidden;
+ --line-comment-z: 5;
+ --line-comment-popover-z: 30;
+ }
}
diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx
index 84ec934e2..b5a359707 100644
--- a/packages/ui/src/components/session-review.tsx
+++ b/packages/ui/src/components/session-review.tsx
@@ -290,8 +290,8 @@ export const SessionReview = (props: SessionReviewProps) => {
<div data-slot="session-review-title">{i18n.t("ui.sessionReview.title")}</div>
<div data-slot="session-review-actions">
<Show when={props.onDiffStyleChange}>
- <RadioGroup
- options={["unified", "split"] as const}
+ <RadioGroup<SessionReviewDiffStyle>
+ options={["unified", "split"]}
current={diffStyle()}
value={(style) => style}
label={(style) =>
@@ -501,6 +501,7 @@ export const SessionReview = (props: SessionReviewProps) => {
value={diff.file}
id={diffId(diff.file)}
data-file={diff.file}
+ expanded={open().includes(diff.file)}
data-slot="session-review-accordion-item"
data-selected={props.focusedFile === diff.file ? "" : undefined}
>
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css
index d1ade879e..db73e801e 100644
--- a/packages/ui/src/components/session-turn.css
+++ b/packages/ui/src/components/session-turn.css
@@ -1,572 +1,580 @@
[data-component="session-turn"] {
- --session-turn-sticky-height: 0px;
- --sticky-header-height: calc(var(--session-title-height, 0px) + var(--session-turn-sticky-height, 0px) + 24px);
- /* flex: 1; */
- height: 100%;
- min-height: 0;
- min-width: 0;
- display: flex;
- align-items: flex-start;
- justify-content: flex-start;
-
- [data-slot="session-turn-content"] {
- flex-grow: 1;
- width: 100%;
- height: 100%;
- min-width: 0;
- overflow-y: auto;
- scrollbar-width: none;
- }
-
- [data-slot="session-turn-content"]::-webkit-scrollbar {
- display: none;
- }
-
- [data-slot="session-turn-message-container"] {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- align-self: stretch;
- min-width: 0;
- gap: 18px;
- overflow-anchor: none;
-
- [data-slot="session-turn-badge"] {
- display: inline-flex;
- align-items: center;
- padding: 2px 6px;
- border-radius: 4px;
- font-family: var(--font-family-mono);
- font-size: var(--font-size-x-small);
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-normal);
- white-space: nowrap;
- color: var(--text-base);
- background: var(--surface-raised-base);
- }
- }
-
- [data-slot="session-turn-attachments"] {
- width: 100%;
- min-width: 0;
- align-self: stretch;
- }
-
- [data-slot="session-turn-sticky"] {
- width: calc(100% + 9px);
- position: sticky;
- top: var(--session-title-height, 0px);
- z-index: 20;
- background-color: var(--background-stronger);
- margin-left: -9px;
- padding-left: 9px;
- /* padding-bottom: 12px; */
- display: flex;
- flex-direction: column;
- gap: 12px;
-
- &::before {
- content: "";
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- background-color: var(--background-stronger);
- z-index: -1;
- }
-
- &::after {
- content: "";
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- height: 32px;
- background: linear-gradient(to bottom, var(--background-stronger), transparent);
- pointer-events: none;
- }
- }
-
- [data-slot="session-turn-message-header"] {
- display: flex;
- align-items: center;
- align-self: stretch;
- height: 32px;
- }
-
- [data-slot="session-turn-message-content"] {
- margin-top: 0;
- max-width: 100%;
- }
-
- [data-component="user-message"] [data-slot="user-message-text"] {
- max-height: var(--user-message-collapsed-height, 64px);
- }
-
- [data-component="user-message"][data-expanded="true"] [data-slot="user-message-text"] {
- max-height: none;
- }
-
- [data-component="user-message"][data-can-expand="true"] [data-slot="user-message-text"] {
- padding-right: 36px;
- padding-bottom: 28px;
- }
-
- [data-component="user-message"][data-can-expand="true"]:not([data-expanded="true"])
- [data-slot="user-message-text"]::after {
- content: "";
- position: absolute;
- left: 0;
- right: 0;
- height: 8px;
- bottom: 0px;
- background:
- linear-gradient(to bottom, transparent, var(--surface-weak)),
- linear-gradient(to bottom, transparent, var(--surface-weak));
- pointer-events: none;
- }
-
- [data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"] {
- display: none;
- position: absolute;
- bottom: 6px;
- right: 6px;
- padding: 0;
- }
-
- [data-component="user-message"][data-can-expand="true"]
- [data-slot="user-message-text"]
- [data-slot="user-message-expand"],
- [data-component="user-message"][data-expanded="true"]
- [data-slot="user-message-text"]
- [data-slot="user-message-expand"] {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- height: 22px;
- width: 22px;
- border: none;
- border-radius: 6px;
- background: transparent;
- cursor: pointer;
- color: var(--text-weak);
-
- [data-slot="icon-svg"] {
- transition: transform 0.15s ease;
- }
- }
-
- [data-component="user-message"][data-expanded="true"]
- [data-slot="user-message-text"]
- [data-slot="user-message-expand"]
- [data-slot="icon-svg"] {
- transform: rotate(180deg);
- }
-
- [data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"]:hover {
- background: var(--surface-raised-base);
- color: var(--text-base);
- }
-
- [data-slot="session-turn-user-badges"] {
- display: flex;
- align-items: center;
- gap: 6px;
- padding-left: 16px;
- }
-
- [data-slot="session-turn-message-title"] {
- width: 100%;
- font-size: var(--font-size-large);
- font-weight: 500;
- color: var(--text-strong);
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
- white-space: nowrap;
- }
-
- [data-slot="session-turn-message-title"] h1 {
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
- white-space: nowrap;
- font-size: inherit;
- font-weight: inherit;
- }
-
- [data-slot="session-turn-typewriter"] {
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
- white-space: nowrap;
- }
-
- [data-slot="session-turn-summary-section"] {
- width: 100%;
- display: flex;
- flex-direction: column;
- gap: 24px;
- align-items: flex-start;
- align-self: stretch;
- }
-
- [data-slot="session-turn-summary-header"] {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 4px;
- align-self: stretch;
-
- [data-slot="session-turn-response"] {
- position: relative;
- width: 100%;
- }
-
- [data-slot="session-turn-response-copy-wrapper"] {
- position: absolute;
- top: 8px;
- right: 8px;
- opacity: 0;
- transition: opacity 0.15s ease;
- z-index: 1;
- }
-
- [data-slot="session-turn-response"]:hover [data-slot="session-turn-response-copy-wrapper"] {
- opacity: 1;
- }
-
- p {
- font-size: var(--font-size-base);
- line-height: var(--line-height-x-large);
- }
- }
-
- [data-slot="session-turn-summary-title"] {
- font-size: 13px;
- /* text-12-medium */
- font-weight: 500;
- color: var(--text-weak);
- }
-
- [data-slot="session-turn-markdown"],
- [data-slot="session-turn-accordion"] [data-slot="accordion-content"] {
- -webkit-user-select: text;
- user-select: text;
- }
-
- [data-slot="session-turn-markdown"] {
- &[data-diffs="true"] {
- font-size: 15px;
- }
-
- &[data-fade="true"] > * {
- animation: fadeUp 0.4s ease-out forwards;
- opacity: 0;
-
- &:nth-child(1) {
- animation-delay: 0.1s;
- }
-
- &:nth-child(2) {
- animation-delay: 0.2s;
- }
-
- &:nth-child(3) {
- animation-delay: 0.3s;
- }
-
- &:nth-child(4) {
- animation-delay: 0.4s;
- }
-
- &:nth-child(5) {
- animation-delay: 0.5s;
- }
-
- &:nth-child(6) {
- animation-delay: 0.6s;
- }
-
- &:nth-child(7) {
- animation-delay: 0.7s;
- }
-
- &:nth-child(8) {
- animation-delay: 0.8s;
- }
-
- &:nth-child(9) {
- animation-delay: 0.9s;
- }
-
- &:nth-child(10) {
- animation-delay: 1s;
- }
-
- &:nth-child(11) {
- animation-delay: 1.1s;
- }
-
- &:nth-child(12) {
- animation-delay: 1.2s;
- }
-
- &:nth-child(13) {
- animation-delay: 1.3s;
- }
-
- &:nth-child(14) {
- animation-delay: 1.4s;
- }
-
- &:nth-child(15) {
- animation-delay: 1.5s;
- }
-
- &:nth-child(16) {
- animation-delay: 1.6s;
- }
-
- &:nth-child(17) {
- animation-delay: 1.7s;
- }
-
- &:nth-child(18) {
- animation-delay: 1.8s;
- }
-
- &:nth-child(19) {
- animation-delay: 1.9s;
- }
-
- &:nth-child(20) {
- animation-delay: 2s;
- }
-
- &:nth-child(21) {
- animation-delay: 2.1s;
- }
-
- &:nth-child(22) {
- animation-delay: 2.2s;
- }
-
- &:nth-child(23) {
- animation-delay: 2.3s;
- }
-
- &:nth-child(24) {
- animation-delay: 2.4s;
- }
-
- &:nth-child(25) {
- animation-delay: 2.5s;
- }
-
- &:nth-child(26) {
- animation-delay: 2.6s;
- }
-
- &:nth-child(27) {
- animation-delay: 2.7s;
- }
-
- &:nth-child(28) {
- animation-delay: 2.8s;
- }
-
- &:nth-child(29) {
- animation-delay: 2.9s;
- }
-
- &:nth-child(30) {
- animation-delay: 3s;
- }
- }
- }
-
- [data-slot="session-turn-summary-section"] {
- position: relative;
-
- [data-slot="session-turn-summary-copy"] {
- position: absolute;
- top: 0;
- right: 0;
- opacity: 0;
- transition: opacity 0.15s ease;
- }
-
- &:hover [data-slot="session-turn-summary-copy"] {
- opacity: 1;
- }
- }
-
- [data-slot="session-turn-accordion"] {
- width: 100%;
- }
-
- [data-component="sticky-accordion-header"] {
- top: var(--sticky-header-height, 0px);
-
- &[data-expanded]::before {
- top: calc(-1 * var(--sticky-header-height, 0px));
- }
- }
-
- [data-slot="session-turn-accordion-trigger-content"] {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- gap: 20px;
-
- [data-expandable="false"] {
- pointer-events: none;
- }
- }
-
- [data-slot="session-turn-file-info"] {
- flex-grow: 1;
- display: flex;
- align-items: center;
- gap: 20px;
- min-width: 0;
- }
-
- [data-slot="session-turn-file-icon"] {
- flex-shrink: 0;
- width: 16px;
- height: 16px;
- }
-
- [data-slot="session-turn-file-path"] {
- display: flex;
- flex-grow: 1;
- min-width: 0;
- }
-
- [data-slot="session-turn-directory"] {
- color: var(--text-base);
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- direction: rtl;
- text-align: left;
- }
-
- [data-slot="session-turn-filename"] {
- color: var(--text-strong);
- flex-shrink: 0;
- }
-
- [data-slot="session-turn-accordion-actions"] {
- flex-shrink: 0;
- display: flex;
- gap: 16px;
- align-items: center;
- justify-content: flex-end;
- }
-
- [data-slot="session-turn-accordion-content"] {
- max-height: 240px;
- /* max-h-60 */
- overflow-y: auto;
- scrollbar-width: none;
- }
-
- [data-slot="session-turn-accordion-content"]::-webkit-scrollbar {
- display: none;
- }
-
- [data-slot="session-turn-response-section"] {
- width: calc(100% + 9px);
- min-width: 0;
- margin-left: -9px;
- padding-left: 9px;
- }
-
- [data-slot="session-turn-collapsible"] {
- gap: 32px;
- overflow: visible;
- }
-
- [data-slot="session-turn-collapsible-trigger-content"] {
- max-width: 100%;
- display: flex;
- align-items: center;
- gap: 8px;
- color: var(--text-weak);
-
- [data-slot="session-turn-trigger-icon"] {
- color: var(--icon-base);
- }
-
- [data-component="spinner"] {
- width: 12px;
- height: 12px;
- margin-right: 4px;
- }
-
- [data-component="icon"] {
- width: 14px;
- height: 14px;
- }
- }
-
- [data-slot="session-turn-retry-message"] {
- font-weight: 500;
- color: var(--syntax-critical);
- }
-
- [data-slot="session-turn-retry-seconds"] {
- color: var(--text-weak);
- }
-
- [data-slot="session-turn-retry-attempt"] {
- color: var(--text-weak);
- }
-
- [data-slot="session-turn-status-text"] {
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- [data-slot="session-turn-details-text"] {
- font-size: 13px;
- /* text-12-medium */
- font-weight: 500;
- }
-
- .error-card {
- color: var(--text-on-critical-base);
- max-height: 240px;
- overflow-y: auto;
- }
-
- [data-slot="session-turn-collapsible-content-inner"] {
- width: 100%;
- min-width: 0;
- display: flex;
- flex-direction: column;
- align-self: stretch;
- gap: 12px;
- margin-left: 12px;
- padding-left: 12px;
- padding-right: 12px;
- border-left: 1px solid var(--border-base);
-
- > :first-child > [data-component="markdown"]:first-child {
- margin-top: 0;
- }
- }
-
- [data-slot="session-turn-permission-parts"] {
- width: 100%;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
+ --session-turn-sticky-height: 0px;
+ --sticky-header-height: calc(
+ var(--session-title-height, 0px) +
+ var(--session-turn-sticky-height, 0px) +
+ 24px
+ );
+ /* flex: 1; */
+ height: 100%;
+ min-height: 0;
+ min-width: 0;
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-start;
+
+ [data-slot="session-turn-content"] {
+ flex-grow: 1;
+ width: 100%;
+ height: 100%;
+ min-width: 0;
+ overflow-y: auto;
+ scrollbar-width: none;
+ }
+
+ [data-slot="session-turn-content"]::-webkit-scrollbar {
+ display: none;
+ }
+
+ [data-slot="session-turn-message-container"] {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ align-self: stretch;
+ min-width: 0;
+ gap: 18px;
+ overflow-anchor: none;
+
+ [data-slot="session-turn-badge"] {
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-family: var(--font-family-mono);
+ font-size: var(--font-size-x-small);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-normal);
+ white-space: nowrap;
+ color: var(--text-base);
+ background: var(--surface-raised-base);
+ }
+ }
+
+ [data-slot="session-turn-attachments"] {
+ width: 100%;
+ min-width: 0;
+ align-self: stretch;
+ }
+
+ [data-slot="session-turn-sticky"] {
+ width: calc(100% + 9px);
+ position: sticky;
+ top: var(--session-title-height, 0px);
+ z-index: 20;
+ background-color: var(--background-stronger);
+ margin-left: -9px;
+ padding-left: 9px;
+ /* padding-bottom: 12px; */
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+
+ &::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: var(--background-stronger);
+ z-index: -1;
+ }
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ height: 32px;
+ background: linear-gradient(
+ to bottom,
+ var(--background-stronger),
+ transparent
+ );
+ pointer-events: none;
+ }
+ }
+
+ [data-slot="session-turn-message-header"] {
+ display: flex;
+ align-items: center;
+ align-self: stretch;
+ height: 32px;
+ }
+
+ [data-slot="session-turn-message-content"] {
+ margin-top: 0;
+ max-width: 100%;
+ }
+
+ [data-component="user-message"] [data-slot="user-message-text"] {
+ max-height: var(--user-message-collapsed-height, 64px);
+ transition: max-height 200ms cubic-bezier(0.25, 0, 0.5, 1);
+ }
+
+ [data-component="user-message"][data-expanded="true"]
+ [data-slot="user-message-text"] {
+ max-height: 2000px;
+ }
+
+ [data-component="user-message"][data-can-expand="true"]
+ [data-slot="user-message-text"] {
+ padding-right: 36px;
+ padding-bottom: 28px;
+ }
+
+ [data-component="user-message"][data-can-expand="true"]:not(
+ [data-expanded="true"]
+ )
+ [data-slot="user-message-text"]::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ height: 8px;
+ bottom: 0px;
+ background:
+ linear-gradient(to bottom, transparent, var(--surface-weak)),
+ linear-gradient(to bottom, transparent, var(--surface-weak));
+ pointer-events: none;
+ }
+
+ [data-component="user-message"]
+ [data-slot="user-message-text"]
+ [data-slot="user-message-expand"] {
+ display: none;
+ position: absolute;
+ bottom: 6px;
+ right: 6px;
+ padding: 0;
+ }
+
+ [data-component="user-message"][data-can-expand="true"]
+ [data-slot="user-message-text"]
+ [data-slot="user-message-expand"],
+ [data-component="user-message"][data-expanded="true"]
+ [data-slot="user-message-text"]
+ [data-slot="user-message-expand"] {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ height: 22px;
+ width: 22px;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ cursor: pointer;
+ color: var(--text-weak);
+ }
+
+ [data-component="user-message"]
+ [data-slot="user-message-text"]
+ [data-slot="user-message-expand"]:hover {
+ background: var(--surface-raised-base);
+ color: var(--text-base);
+ }
+
+ [data-slot="session-turn-user-badges"] {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding-left: 16px;
+ }
+
+ [data-slot="session-turn-message-title"] {
+ width: 100%;
+ font-size: var(--font-size-large);
+ font-weight: 500;
+ color: var(--text-strong);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+ white-space: nowrap;
+ }
+
+ [data-slot="session-turn-message-title"] h1 {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+ white-space: nowrap;
+ font-size: inherit;
+ font-weight: inherit;
+ }
+
+ [data-slot="session-turn-typewriter"] {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+ white-space: nowrap;
+ }
+
+ [data-slot="session-turn-summary-section"] {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ align-items: flex-start;
+ align-self: stretch;
+ }
+
+ [data-slot="session-turn-summary-header"] {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 4px;
+ align-self: stretch;
+
+ [data-slot="session-turn-response"] {
+ position: relative;
+ width: 100%;
+ }
+
+ [data-slot="session-turn-response-copy-wrapper"] {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ z-index: 1;
+ }
+
+ [data-slot="session-turn-response"]:hover
+ [data-slot="session-turn-response-copy-wrapper"] {
+ opacity: 1;
+ }
+
+ p {
+ font-size: var(--font-size-base);
+ line-height: var(--line-height-x-large);
+ }
+ }
+
+ [data-slot="session-turn-summary-title"] {
+ font-size: 13px;
+ /* text-12-medium */
+ font-weight: 500;
+ color: var(--text-weak);
+ }
+
+ [data-slot="session-turn-markdown"],
+ [data-slot="session-turn-accordion"] [data-slot="accordion-content"] {
+ -webkit-user-select: text;
+ user-select: text;
+ }
+
+ [data-slot="session-turn-markdown"] {
+ &[data-diffs="true"] {
+ font-size: 15px;
+ }
+
+ &[data-fade="true"] > * {
+ animation: fadeUp 0.4s ease-out forwards;
+ opacity: 0;
+
+ &:nth-child(1) {
+ animation-delay: 0.1s;
+ }
+
+ &:nth-child(2) {
+ animation-delay: 0.2s;
+ }
+
+ &:nth-child(3) {
+ animation-delay: 0.3s;
+ }
+
+ &:nth-child(4) {
+ animation-delay: 0.4s;
+ }
+
+ &:nth-child(5) {
+ animation-delay: 0.5s;
+ }
+
+ &:nth-child(6) {
+ animation-delay: 0.6s;
+ }
+
+ &:nth-child(7) {
+ animation-delay: 0.7s;
+ }
+
+ &:nth-child(8) {
+ animation-delay: 0.8s;
+ }
+
+ &:nth-child(9) {
+ animation-delay: 0.9s;
+ }
+
+ &:nth-child(10) {
+ animation-delay: 1s;
+ }
+
+ &:nth-child(11) {
+ animation-delay: 1.1s;
+ }
+
+ &:nth-child(12) {
+ animation-delay: 1.2s;
+ }
+
+ &:nth-child(13) {
+ animation-delay: 1.3s;
+ }
+
+ &:nth-child(14) {
+ animation-delay: 1.4s;
+ }
+
+ &:nth-child(15) {
+ animation-delay: 1.5s;
+ }
+
+ &:nth-child(16) {
+ animation-delay: 1.6s;
+ }
+
+ &:nth-child(17) {
+ animation-delay: 1.7s;
+ }
+
+ &:nth-child(18) {
+ animation-delay: 1.8s;
+ }
+
+ &:nth-child(19) {
+ animation-delay: 1.9s;
+ }
+
+ &:nth-child(20) {
+ animation-delay: 2s;
+ }
+
+ &:nth-child(21) {
+ animation-delay: 2.1s;
+ }
+
+ &:nth-child(22) {
+ animation-delay: 2.2s;
+ }
+
+ &:nth-child(23) {
+ animation-delay: 2.3s;
+ }
+
+ &:nth-child(24) {
+ animation-delay: 2.4s;
+ }
+
+ &:nth-child(25) {
+ animation-delay: 2.5s;
+ }
+
+ &:nth-child(26) {
+ animation-delay: 2.6s;
+ }
+
+ &:nth-child(27) {
+ animation-delay: 2.7s;
+ }
+
+ &:nth-child(28) {
+ animation-delay: 2.8s;
+ }
+
+ &:nth-child(29) {
+ animation-delay: 2.9s;
+ }
+
+ &:nth-child(30) {
+ animation-delay: 3s;
+ }
+ }
+ }
+
+ [data-slot="session-turn-summary-section"] {
+ position: relative;
+
+ [data-slot="session-turn-summary-copy"] {
+ position: absolute;
+ top: 0;
+ right: 0;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ }
+
+ &:hover [data-slot="session-turn-summary-copy"] {
+ opacity: 1;
+ }
+ }
+
+ [data-slot="session-turn-accordion"] {
+ width: 100%;
+ }
+
+ [data-component="sticky-accordion-header"] {
+ top: var(--sticky-header-height, 0px);
+
+ &[data-expanded]::before {
+ top: calc(-1 * var(--sticky-header-height, 0px));
+ }
+ }
+
+ [data-slot="session-turn-accordion-trigger-content"] {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ gap: 20px;
+
+ [data-expandable="false"] {
+ pointer-events: none;
+ }
+ }
+
+ [data-slot="session-turn-file-info"] {
+ flex-grow: 1;
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ min-width: 0;
+ }
+
+ [data-slot="session-turn-file-icon"] {
+ flex-shrink: 0;
+ width: 16px;
+ height: 16px;
+ }
+
+ [data-slot="session-turn-file-path"] {
+ display: flex;
+ flex-grow: 1;
+ min-width: 0;
+ }
+
+ [data-slot="session-turn-directory"] {
+ color: var(--text-base);
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ direction: rtl;
+ text-align: left;
+ }
+
+ [data-slot="session-turn-filename"] {
+ color: var(--text-strong);
+ flex-shrink: 0;
+ }
+
+ [data-slot="session-turn-accordion-actions"] {
+ flex-shrink: 0;
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ justify-content: flex-end;
+ color: var(--icon-base);
+ }
+
+ [data-slot="session-turn-accordion-content"] {
+ max-height: 240px;
+ /* max-h-60 */
+ overflow-y: auto;
+ scrollbar-width: none;
+ }
+
+ [data-slot="session-turn-accordion-content"]::-webkit-scrollbar {
+ display: none;
+ }
+
+ [data-slot="session-turn-response-section"] {
+ width: calc(100% + 9px);
+ min-width: 0;
+ margin-left: -9px;
+ padding-left: 9px;
+ }
+
+ [data-slot="session-turn-collapsible"] {
+ gap: 32px;
+ overflow: visible;
+ }
+
+ [data-slot="session-turn-collapsible-trigger-content"] {
+ max-width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: var(--text-weak);
+
+ [data-slot="session-turn-trigger-icon"] {
+ color: var(--icon-base);
+ }
+
+ [data-component="spinner"] {
+ width: 12px;
+ height: 12px;
+ margin-right: 4px;
+ }
+
+ [data-component="icon"] {
+ width: 14px;
+ height: 14px;
+ }
+ }
+
+ [data-slot="session-turn-retry-message"] {
+ font-weight: 500;
+ color: var(--syntax-critical);
+ }
+
+ [data-slot="session-turn-retry-seconds"] {
+ color: var(--text-weak);
+ }
+
+ [data-slot="session-turn-retry-attempt"] {
+ color: var(--text-weak);
+ }
+
+ [data-slot="session-turn-status-text"] {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ [data-slot="session-turn-details-text"] {
+ font-size: 13px;
+ /* text-12-medium */
+ font-weight: 500;
+ }
+
+ .error-card {
+ color: var(--text-on-critical-base);
+ max-height: 240px;
+ overflow-y: auto;
+ }
+
+ [data-slot="session-turn-collapsible-content-inner"] {
+ width: 100%;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ align-self: stretch;
+ gap: 12px;
+ margin-left: 12px;
+ padding-left: 12px;
+ padding-right: 12px;
+ border-left: 1px solid var(--border-base);
+
+ > :first-child > [data-component="markdown"]:first-child {
+ margin-top: 0;
+ }
+ }
+
+ [data-slot="session-turn-permission-parts"] {
+ width: 100%;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
}
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index 3f176db70..af06757f2 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -553,7 +553,7 @@ export function SessionTurn(
data-slot="session-turn-collapsible-trigger-content"
variant="ghost"
size="small"
- onClick={props.onStepsExpandedToggle ?? (() => {})}
+ onClick={props.onStepsExpandedToggle ?? (() => { })}
aria-expanded={props.stepsExpanded}
>
<Switch>
diff --git a/packages/ui/src/components/spinner.css b/packages/ui/src/components/spinner.css
index 2ca474dc3..928a21c5e 100644
--- a/packages/ui/src/components/spinner.css
+++ b/packages/ui/src/components/spinner.css
@@ -1,6 +1,6 @@
[data-component="spinner"] {
- color: inherit;
- flex-shrink: 0;
- width: 18px;
- aspect-ratio: 1;
+ color: inherit;
+ flex-shrink: 0;
+ width: 18px;
+ aspect-ratio: 1;
}
diff --git a/packages/ui/src/components/sticky-accordion-header.css b/packages/ui/src/components/sticky-accordion-header.css
index 0fbc354fb..acf9eb703 100644
--- a/packages/ui/src/components/sticky-accordion-header.css
+++ b/packages/ui/src/components/sticky-accordion-header.css
@@ -1,16 +1,16 @@
[data-component="sticky-accordion-header"] {
- position: sticky;
- top: 0px;
+ position: sticky;
+ top: 0px;
- &[data-expanded] {
- z-index: 10;
+ &[data-expanded] {
+ z-index: 10;
- &::before {
- content: "";
- z-index: -10;
- position: absolute;
- inset: 0;
- background-color: var(--background-stronger);
- }
- }
+ &::before {
+ content: "";
+ z-index: -10;
+ position: absolute;
+ inset: 0;
+ background-color: var(--background-stronger);
+ }
+ }
}
diff --git a/packages/ui/src/components/switch.css b/packages/ui/src/components/switch.css
index 89e844732..4951e5998 100644
--- a/packages/ui/src/components/switch.css
+++ b/packages/ui/src/components/switch.css
@@ -1,132 +1,133 @@
[data-component="switch"] {
- position: relative;
- display: flex;
- align-items: center;
- gap: 8px;
- cursor: default;
-
- [data-slot="switch-input"] {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border-width: 0;
- }
-
- [data-slot="switch-control"] {
- display: inline-flex;
- align-items: center;
- width: 28px;
- height: 16px;
- flex-shrink: 0;
- border-radius: 3px;
- border: 1px solid var(--border-weak-base);
- background: var(--surface-base);
- transition:
- background-color 150ms,
- border-color 150ms;
- }
-
- [data-slot="switch-thumb"] {
- width: 14px;
- height: 14px;
- box-sizing: content-box;
-
- border-radius: 2px;
- border: 1px solid var(--border-base);
- background: var(--icon-invert-base);
-
- /* shadows/shadow-xs */
- box-shadow:
- 0 1px 2px -1px rgba(19, 16, 16, 0.04),
- 0 1px 2px 0 rgba(19, 16, 16, 0.06),
- 0 1px 3px 0 rgba(19, 16, 16, 0.08);
-
- transform: translateX(-1px);
- transition:
- transform 150ms,
- background-color 150ms;
- }
-
- [data-slot="switch-label"] {
- user-select: none;
- color: var(--text-base);
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
- }
-
- [data-slot="switch-description"] {
- color: var(--text-base);
- font-family: var(--font-family-sans);
- font-size: 12px;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-normal);
- letter-spacing: var(--letter-spacing-normal);
- }
-
- [data-slot="switch-error"] {
- color: var(--text-error);
- font-family: var(--font-family-sans);
- font-size: 12px;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-normal);
- letter-spacing: var(--letter-spacing-normal);
- }
-
- &:hover:not([data-disabled], [data-readonly]) [data-slot="switch-control"] {
- border-color: var(--border-hover);
- background-color: var(--surface-hover);
- }
-
- &:focus-within:not([data-readonly]) [data-slot="switch-control"] {
- border-color: var(--border-focus);
- box-shadow: 0 0 0 2px var(--surface-focus);
- }
-
- &[data-checked] [data-slot="switch-control"] {
- box-sizing: border-box;
- border-color: var(--icon-strong-base);
- background-color: var(--icon-strong-base);
- }
-
- &[data-checked] [data-slot="switch-thumb"] {
- border: none;
- transform: translateX(12px);
- background-color: var(--icon-invert-base);
- }
-
- &[data-checked]:hover:not([data-disabled], [data-readonly]) [data-slot="switch-control"] {
- border-color: var(--border-hover);
- background-color: var(--surface-hover);
- }
-
- &[data-disabled] {
- cursor: not-allowed;
- }
-
- &[data-disabled] [data-slot="switch-control"] {
- border-color: var(--border-disabled);
- background-color: var(--surface-disabled);
- }
-
- &[data-disabled] [data-slot="switch-thumb"] {
- background-color: var(--icon-disabled);
- }
-
- &[data-invalid] [data-slot="switch-control"] {
- border-color: var(--border-error);
- }
-
- &[data-readonly] {
- cursor: default;
- pointer-events: none;
- }
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: default;
+
+ [data-slot="switch-input"] {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+ }
+
+ [data-slot="switch-control"] {
+ display: inline-flex;
+ align-items: center;
+ width: 28px;
+ height: 16px;
+ flex-shrink: 0;
+ border-radius: 3px;
+ border: 1px solid var(--border-weak-base);
+ background: var(--surface-base);
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ }
+
+ [data-slot="switch-thumb"] {
+ width: 14px;
+ height: 14px;
+ box-sizing: content-box;
+
+ border-radius: 2px;
+ border: 1px solid var(--border-base);
+ background: var(--icon-invert-base);
+
+ /* shadows/shadow-xs */
+ box-shadow:
+ 0 1px 2px -1px rgba(19, 16, 16, 0.04),
+ 0 1px 2px 0 rgba(19, 16, 16, 0.06),
+ 0 1px 3px 0 rgba(19, 16, 16, 0.08);
+
+ transform: translateX(-1px);
+ transition-property: transform, background-color, border-color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+ }
+
+ [data-slot="switch-label"] {
+ user-select: none;
+ color: var(--text-base);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ [data-slot="switch-description"] {
+ color: var(--text-base);
+ font-family: var(--font-family-sans);
+ font-size: 12px;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-normal);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ [data-slot="switch-error"] {
+ color: var(--text-error);
+ font-family: var(--font-family-sans);
+ font-size: 12px;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-normal);
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ &:hover:not([data-disabled], [data-readonly]) [data-slot="switch-control"] {
+ border-color: var(--border-hover);
+ background-color: var(--surface-hover);
+ }
+
+ &:focus-within:not([data-readonly]) [data-slot="switch-control"] {
+ border-color: var(--border-focus);
+ box-shadow: 0 0 0 2px var(--surface-focus);
+ }
+
+ &[data-checked] [data-slot="switch-control"] {
+ box-sizing: border-box;
+ border-color: var(--icon-strong-base);
+ background-color: var(--icon-strong-base);
+ }
+
+ &[data-checked] [data-slot="switch-thumb"] {
+ border: none;
+ transform: translateX(12px);
+ background-color: var(--icon-invert-base);
+ }
+
+ &[data-checked]:hover:not([data-disabled], [data-readonly])
+ [data-slot="switch-control"] {
+ border-color: var(--border-hover);
+ background-color: var(--surface-hover);
+ }
+
+ &[data-disabled] {
+ cursor: not-allowed;
+ }
+
+ &[data-disabled] [data-slot="switch-control"] {
+ border-color: var(--border-disabled);
+ background-color: var(--surface-disabled);
+ }
+
+ &[data-disabled] [data-slot="switch-thumb"] {
+ background-color: var(--icon-disabled);
+ }
+
+ &[data-invalid] [data-slot="switch-control"] {
+ border-color: var(--border-error);
+ }
+
+ &[data-readonly] {
+ cursor: default;
+ pointer-events: none;
+ }
}
diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css
index 56c3e083f..c7829f722 100644
--- a/packages/ui/src/components/tabs.css
+++ b/packages/ui/src/components/tabs.css
@@ -1,451 +1,454 @@
[data-component="tabs"] {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- background-color: var(--background-stronger);
- overflow: clip;
-
- [data-slot="tabs-list"] {
- height: 48px;
- width: 100%;
- position: relative;
- display: flex;
- align-items: center;
- overflow-x: auto;
-
- /* Hide scrollbar */
- scrollbar-width: none;
- -ms-overflow-style: none;
- &::-webkit-scrollbar {
- display: none;
- }
-
- /* After element to fill remaining space */
- &::after {
- content: "";
- display: block;
- flex-grow: 1;
- height: 100%;
- border-bottom: 1px solid var(--border-weak-base);
- background-color: var(--background-base);
- }
-
- &:empty::after {
- display: none;
- }
- }
-
- [data-slot="tabs-trigger-wrapper"] {
- position: relative;
- height: 100%;
- display: flex;
- align-items: center;
- gap: 12px;
- color: var(--text-base);
-
- /* 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);
-
- white-space: nowrap;
- flex-shrink: 0;
- max-width: 280px;
- border-bottom: 1px solid var(--border-weak-base);
- border-right: 1px solid var(--border-weak-base);
- background-color: var(--background-base);
-
- [data-slot="tabs-trigger"] {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 14px 24px 14px 12px;
- outline: none;
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
-
- &:focus-visible {
- outline: none;
- box-shadow: none;
- }
- }
-
- [data-slot="tabs-trigger-close-button"] {
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- [data-component="icon-button"] {
- margin: -0.25rem;
- }
-
- &:disabled {
- pointer-events: none;
- color: var(--text-weaker);
- }
- &:focus-visible {
- outline: none;
- box-shadow: none;
- }
- &:has([data-hidden]) {
- [data-slot="tabs-trigger-close-button"] {
- opacity: 0;
- }
-
- &:hover {
- [data-slot="tabs-trigger-close-button"] {
- opacity: 1;
- }
- }
- }
- &:has([data-selected]) {
- color: var(--text-strong);
- background-color: transparent;
- border-bottom-color: transparent;
- [data-slot="tabs-trigger-close-button"] {
- opacity: 1;
- }
- }
- &:hover:not(:disabled):not([data-selected]) {
- color: var(--text-strong);
- }
- &:has([data-slot="tabs-trigger-close-button"]) {
- padding-right: 12px;
-
- [data-slot="tabs-trigger"] {
- padding-right: 0;
- }
- }
- }
-
- [data-slot="tabs-content"] {
- overflow-y: auto;
- flex: 1;
-
- /* Hide scrollbar */
- scrollbar-width: none;
- -ms-overflow-style: none;
- &::-webkit-scrollbar {
- display: none;
- }
-
- &:focus-visible {
- outline: none;
- }
- }
-
- &[data-variant="alt"] {
- [data-slot="tabs-list"] {
- padding-left: 24px;
- padding-right: 24px;
- gap: 12px;
- border-bottom: 1px solid var(--border-weak-base);
- background-color: transparent;
-
- &::after {
- border: none;
- background-color: transparent;
- }
- &:empty::after {
- display: none;
- }
- }
-
- [data-slot="tabs-trigger-wrapper"] {
- border: none;
- color: var(--text-base);
- background-color: transparent;
- border-bottom-width: 2px;
- border-bottom-style: solid;
- border-bottom-color: transparent;
- gap: 4px;
-
- /* text-14-regular */
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-x-large); /* 171.429% */
- letter-spacing: var(--letter-spacing-normal);
-
- [data-slot="tabs-trigger"] {
- height: 100%;
- padding: 4px;
- background-color: transparent;
- border-bottom-width: 2px;
- border-bottom-color: transparent;
- }
-
- [data-slot="tabs-trigger-close-button"] {
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- [data-component="icon-button"] {
- width: 16px;
- height: 16px;
- margin: 0;
- }
-
- &:has([data-selected]) {
- color: var(--text-strong);
- background-color: transparent;
- border-bottom-color: var(--icon-strong-base);
- }
-
- &:hover:not(:disabled):not([data-selected]) {
- color: var(--text-strong);
- }
-
- &:has([data-slot="tabs-trigger-close-button"]) {
- padding-right: 0;
- [data-slot="tabs-trigger"] {
- padding-right: 0;
- }
- }
- }
-
- /* [data-slot="tabs-content"] { */
- /* } */
- }
-
- &[data-variant="pill"][data-orientation="horizontal"] {
- background-color: transparent;
-
- [data-slot="tabs-list"] {
- height: auto;
- padding: 6px 0;
- gap: 4px;
- background-color: var(--background-base);
-
- &::after {
- display: none;
- }
- }
-
- [data-slot="tabs-trigger-wrapper"] {
- height: 32px;
- border: none;
- border-radius: var(--radius-sm);
- background-color: transparent;
- gap: 0;
-
- /* text-13-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);
- letter-spacing: var(--letter-spacing-normal);
-
- [data-slot="tabs-trigger"] {
- height: 100%;
- width: 100%;
- padding: 0 12px;
- background-color: transparent;
- }
-
- &:hover:not(:disabled) {
- background-color: var(--surface-raised-base-hover);
- color: var(--text-strong);
- }
-
- &:has([data-selected]) {
- background-color: var(--surface-raised-base-active);
- color: var(--text-strong);
-
- &:hover:not(:disabled) {
- background-color: var(--surface-raised-base-active);
- }
- }
- }
- }
-
- &[data-variant="pill"][data-orientation="horizontal"][data-scope="filetree"] {
- [data-slot="tabs-list"] {
- height: 48px;
- padding-inline: 12px;
- gap: 8px;
- align-items: center;
- }
-
- [data-slot="tabs-trigger-wrapper"] {
- height: 26px;
- border-radius: 6px;
- color: var(--text-weak);
-
- &:not(:has([data-selected])):hover:not(:disabled) {
- color: var(--text-base);
- }
-
- &:has([data-selected]) {
- color: var(--text-strong);
- }
- }
- }
-
- &[data-orientation="vertical"] {
- flex-direction: row;
-
- [data-slot="tabs-list"] {
- flex-direction: column;
- width: auto;
- height: 100%;
- overflow-x: hidden;
- overflow-y: auto;
- padding: 8px;
- gap: 4px;
- background-color: var(--background-base);
- border-right: 1px solid var(--border-weak-base);
-
- &::after {
- display: none;
- }
- }
-
- [data-slot="tabs-trigger-wrapper"] {
- width: 100%;
- height: 32px;
- border: none;
- border-radius: 8px;
- background-color: transparent;
-
- [data-slot="tabs-trigger"] {
- height: 100%;
- padding: 0 8px;
- gap: 8px;
- justify-content: flex-start;
- }
-
- &:hover:not(:disabled) {
- background-color: var(--surface-raised-base-hover);
- }
-
- &:has([data-selected]) {
- background-color: var(--surface-raised-base-active);
- color: var(--text-strong);
- }
- }
-
- [data-slot="tabs-content"] {
- overflow-x: auto;
- overflow-y: auto;
- }
-
- &[data-variant="alt"] {
- [data-slot="tabs-list"] {
- padding: 8px;
- gap: 4px;
- border: none;
-
- &::after {
- display: none;
- }
- }
-
- [data-slot="tabs-trigger-wrapper"] {
- height: 32px;
- border: none;
- border-radius: 8px;
-
- [data-slot="tabs-trigger"] {
- border: none;
- padding: 0 8px;
- gap: 8px;
- justify-content: flex-start;
- }
-
- &:hover:not(:disabled) {
- background-color: var(--surface-raised-base-hover);
- }
-
- &:has([data-selected]) {
- background-color: var(--surface-raised-base-hover);
- color: var(--text-strong);
- }
- }
- }
-
- &[data-variant="settings"] {
- [data-slot="tabs-list"] {
- width: 150px;
- min-width: 150px;
-
- @media (min-width: 640px) {
- width: 200px;
- min-width: 200px;
- }
- padding: 12px;
- gap: 0;
- background-color: var(--background-base);
- border-right: 1px solid var(--border-weak-base);
-
- &::after {
- display: none;
- }
- }
-
- [data-slot="tabs-section-title"] {
- width: 100%;
- padding: 0 0 0 4px;
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-medium);
- color: var(--text-weak);
- }
-
- [data-slot="tabs-trigger-wrapper"] {
- height: 32px;
- border: none;
- border-radius: var(--radius-md);
-
- /* text-14-medium */
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large);
-
- [data-slot="tabs-trigger"] {
- border: none;
- padding: 0 8px;
- gap: 12px;
- justify-content: flex-start;
- width: 100%;
- height: 100%;
- }
-
- [data-component="icon"] {
- color: var(--icon-base);
- }
-
- &:hover:not(:disabled) {
- background-color: var(--surface-raised-base-hover);
- }
-
- &:has([data-selected]) {
- background-color: var(--surface-raised-base-active);
- color: var(--text-strong);
-
- [data-component="icon"] {
- color: var(--icon-strong-base);
- }
-
- &:hover:not(:disabled) {
- background-color: var(--surface-raised-base-active);
- }
- }
- }
-
- [data-slot="tabs-content"] {
- background-color: var(--surface-raised-stronger-non-alpha);
- }
- }
- }
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ background-color: var(--background-stronger);
+ overflow: clip;
+
+ [data-slot="tabs-list"] {
+ height: 48px;
+ width: 100%;
+ position: relative;
+ display: flex;
+ align-items: center;
+ overflow-x: auto;
+
+ /* Hide scrollbar */
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ /* After element to fill remaining space */
+ &::after {
+ content: "";
+ display: block;
+ flex-grow: 1;
+ height: 100%;
+ border-bottom: 1px solid var(--border-weak-base);
+ background-color: var(--background-base);
+ }
+
+ &:empty::after {
+ display: none;
+ }
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ position: relative;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ color: var(--text-base);
+
+ /* 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);
+
+ white-space: nowrap;
+ flex-shrink: 0;
+ max-width: 280px;
+ border-bottom: 1px solid var(--border-weak-base);
+ border-right: 1px solid var(--border-weak-base);
+ background-color: var(--background-base);
+ transition-property: background-color, border-color, color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+
+ [data-slot="tabs-trigger"] {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 14px 24px 14px 12px;
+ outline: none;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ &:focus-visible {
+ outline: none;
+ box-shadow: none;
+ }
+ }
+
+ [data-slot="tabs-trigger-close-button"] {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ [data-component="icon-button"] {
+ margin: -0.25rem;
+ }
+
+ &:disabled {
+ pointer-events: none;
+ color: var(--text-weaker);
+ }
+ &:focus-visible {
+ outline: none;
+ box-shadow: none;
+ }
+ &:has([data-hidden]) {
+ [data-slot="tabs-trigger-close-button"] {
+ opacity: 0;
+ }
+
+ &:hover {
+ [data-slot="tabs-trigger-close-button"] {
+ opacity: 1;
+ }
+ }
+ }
+ &:has([data-selected]) {
+ color: var(--text-strong);
+ background-color: transparent;
+ border-bottom-color: transparent;
+ [data-slot="tabs-trigger-close-button"] {
+ opacity: 1;
+ }
+ }
+ &:hover:not(:disabled):not([data-selected]) {
+ color: var(--text-strong);
+ }
+ &:has([data-slot="tabs-trigger-close-button"]) {
+ padding-right: 12px;
+
+ [data-slot="tabs-trigger"] {
+ padding-right: 0;
+ }
+ }
+ }
+
+ [data-slot="tabs-content"] {
+ overflow-y: auto;
+ flex: 1;
+
+ /* Hide scrollbar */
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ &:focus-visible {
+ outline: none;
+ }
+ }
+
+ &[data-variant="alt"] {
+ [data-slot="tabs-list"] {
+ padding-left: 24px;
+ padding-right: 24px;
+ gap: 12px;
+ border-bottom: 1px solid var(--border-weak-base);
+ background-color: transparent;
+
+ &::after {
+ border: none;
+ background-color: transparent;
+ }
+ &:empty::after {
+ display: none;
+ }
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ border: none;
+ color: var(--text-base);
+ background-color: transparent;
+ border-bottom-width: 2px;
+ border-bottom-style: solid;
+ border-bottom-color: transparent;
+ gap: 4px;
+
+ /* text-14-regular */
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-x-large); /* 171.429% */
+ letter-spacing: var(--letter-spacing-normal);
+
+ [data-slot="tabs-trigger"] {
+ height: 100%;
+ padding: 4px;
+ background-color: transparent;
+ border-bottom-width: 2px;
+ border-bottom-color: transparent;
+ }
+
+ [data-slot="tabs-trigger-close-button"] {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ [data-component="icon-button"] {
+ width: 16px;
+ height: 16px;
+ margin: 0;
+ }
+
+ &:has([data-selected]) {
+ color: var(--text-strong);
+ background-color: transparent;
+ border-bottom-color: var(--icon-strong-base);
+ }
+
+ &:hover:not(:disabled):not([data-selected]) {
+ color: var(--text-strong);
+ }
+
+ &:has([data-slot="tabs-trigger-close-button"]) {
+ padding-right: 0;
+ [data-slot="tabs-trigger"] {
+ padding-right: 0;
+ }
+ }
+ }
+
+ /* [data-slot="tabs-content"] { */
+ /* } */
+ }
+
+ &[data-variant="pill"][data-orientation="horizontal"] {
+ background-color: transparent;
+
+ [data-slot="tabs-list"] {
+ height: auto;
+ padding: 6px 0;
+ gap: 4px;
+ background-color: var(--background-base);
+
+ &::after {
+ display: none;
+ }
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ height: 32px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background-color: transparent;
+ gap: 0;
+
+ /* text-13-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);
+ letter-spacing: var(--letter-spacing-normal);
+
+ [data-slot="tabs-trigger"] {
+ height: 100%;
+ width: 100%;
+ padding: 0 12px;
+ background-color: transparent;
+ }
+
+ &:hover:not(:disabled) {
+ background-color: var(--surface-raised-base-hover);
+ color: var(--text-strong);
+ }
+
+ &:has([data-selected]) {
+ background-color: var(--surface-raised-base-active);
+ color: var(--text-strong);
+
+ &:hover:not(:disabled) {
+ background-color: var(--surface-raised-base-active);
+ }
+ }
+ }
+ }
+
+ &[data-variant="pill"][data-orientation="horizontal"][data-scope="filetree"] {
+ [data-slot="tabs-list"] {
+ height: 48px;
+ padding-inline: 12px;
+ gap: 8px;
+ align-items: center;
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ height: 26px;
+ border-radius: 6px;
+ color: var(--text-weak);
+
+ &:not(:has([data-selected])):hover:not(:disabled) {
+ color: var(--text-base);
+ }
+
+ &:has([data-selected]) {
+ color: var(--text-strong);
+ }
+ }
+ }
+
+ &[data-orientation="vertical"] {
+ flex-direction: row;
+
+ [data-slot="tabs-list"] {
+ flex-direction: column;
+ width: auto;
+ height: 100%;
+ overflow-x: hidden;
+ overflow-y: auto;
+ padding: 8px;
+ gap: 4px;
+ background-color: var(--background-base);
+ border-right: 1px solid var(--border-weak-base);
+
+ &::after {
+ display: none;
+ }
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ width: 100%;
+ height: 32px;
+ border: none;
+ border-radius: 8px;
+ background-color: transparent;
+
+ [data-slot="tabs-trigger"] {
+ height: 100%;
+ padding: 0 8px;
+ gap: 8px;
+ justify-content: flex-start;
+ }
+
+ &:hover:not(:disabled) {
+ background-color: var(--surface-raised-base-hover);
+ }
+
+ &:has([data-selected]) {
+ background-color: var(--surface-raised-base-active);
+ color: var(--text-strong);
+ }
+ }
+
+ [data-slot="tabs-content"] {
+ overflow-x: auto;
+ overflow-y: auto;
+ }
+
+ &[data-variant="alt"] {
+ [data-slot="tabs-list"] {
+ padding: 8px;
+ gap: 4px;
+ border: none;
+
+ &::after {
+ display: none;
+ }
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ height: 32px;
+ border: none;
+ border-radius: 8px;
+
+ [data-slot="tabs-trigger"] {
+ border: none;
+ padding: 0 8px;
+ gap: 8px;
+ justify-content: flex-start;
+ }
+
+ &:hover:not(:disabled) {
+ background-color: var(--surface-raised-base-hover);
+ }
+
+ &:has([data-selected]) {
+ background-color: var(--surface-raised-base-hover);
+ color: var(--text-strong);
+ }
+ }
+ }
+
+ &[data-variant="settings"] {
+ [data-slot="tabs-list"] {
+ width: 150px;
+ min-width: 150px;
+
+ @media (min-width: 640px) {
+ width: 200px;
+ min-width: 200px;
+ }
+ padding: 12px;
+ gap: 0;
+ background-color: var(--background-base);
+ border-right: 1px solid var(--border-weak-base);
+
+ &::after {
+ display: none;
+ }
+ }
+
+ [data-slot="tabs-section-title"] {
+ width: 100%;
+ padding: 0 0 0 4px;
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-medium);
+ color: var(--text-weak);
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ height: 32px;
+ border: none;
+ border-radius: var(--radius-md);
+
+ /* text-14-medium */
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+
+ [data-slot="tabs-trigger"] {
+ border: none;
+ padding: 0 8px;
+ gap: 12px;
+ justify-content: flex-start;
+ width: 100%;
+ height: 100%;
+ }
+
+ [data-component="icon"] {
+ color: var(--icon-base);
+ }
+
+ &:hover:not(:disabled) {
+ background-color: var(--surface-raised-base-hover);
+ }
+
+ &:has([data-selected]) {
+ background-color: var(--surface-raised-base-active);
+ color: var(--text-strong);
+
+ [data-component="icon"] {
+ color: var(--icon-strong-base);
+ }
+
+ &:hover:not(:disabled) {
+ background-color: var(--surface-raised-base-active);
+ }
+ }
+ }
+
+ [data-slot="tabs-content"] {
+ background-color: var(--surface-raised-stronger-non-alpha);
+ }
+ }
+ }
}
diff --git a/packages/ui/src/components/tag.css b/packages/ui/src/components/tag.css
index 0e8b7b9f1..fef2c9ff2 100644
--- a/packages/ui/src/components/tag.css
+++ b/packages/ui/src/components/tag.css
@@ -1,37 +1,40 @@
[data-component="tag"] {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- user-select: none;
+ 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);
+ border-radius: var(--radius-xs);
+ border: 0.5px solid var(--border-weak-base);
+ background: var(--surface-raised-base);
+ color: var(--text-base);
+ transition-property: background-color, border-color, color;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
- &[data-size="normal"] {
- height: 18px;
- padding: 0 6px;
+ &[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);
- }
+ /* 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;
+ &[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);
- }
+ /* 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);
+ }
}
diff --git a/packages/ui/src/components/text-field.css b/packages/ui/src/components/text-field.css
index c94376be7..5568ac2dd 100644
--- a/packages/ui/src/components/text-field.css
+++ b/packages/ui/src/components/text-field.css
@@ -1,134 +1,134 @@
[data-component="input"] {
- width: 100%;
-
- [data-slot="input-input"] {
- width: 100%;
- color: var(--text-strong);
-
- /* text-14-regular */
- font-family: var(--font-family-sans);
- font-size: 14px;
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 142.857% */
- letter-spacing: var(--letter-spacing-normal);
-
- &:focus {
- outline: none;
- }
-
- &::placeholder {
- color: var(--text-weak);
- }
- }
-
- &[data-variant="normal"] {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 8px;
-
- [data-slot="input-label"] {
- color: var(--text-weak);
-
- /* 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: 18px; /* 150% */
- letter-spacing: var(--letter-spacing-normal);
- }
-
- [data-slot="input-wrapper"] {
- display: flex;
- align-items: start;
- justify-content: space-between;
- width: 100%;
- padding-right: 4px;
-
- border-radius: var(--radius-md);
- border: 1px solid var(--border-weak-base);
- background: var(--input-base);
-
- &:focus-within:not(:has([data-readonly])) {
- border-color: transparent;
- /* border/shadow-xs/select */
- box-shadow:
- 0 0 0 3px var(--border-weak-selected),
- 0 0 0 1px var(--border-selected),
- 0 1px 2px -1px rgba(19, 16, 16, 0.25),
- 0 1px 2px 0 rgba(19, 16, 16, 0.08),
- 0 1px 3px 0 rgba(19, 16, 16, 0.12);
- }
-
- &:has([data-invalid]) {
- background: var(--surface-critical-weak);
- border: 1px solid var(--border-critical-selected);
- }
-
- &:not(:has([data-slot="input-copy-button"])) {
- padding-right: 0;
- }
- }
-
- [data-slot="input-input"] {
- color: var(--text-strong);
-
- display: flex;
- height: 32px;
- padding: 2px 12px;
- align-items: center;
- flex: 1;
- min-width: 0;
-
- background: transparent;
- border: none;
-
- /* text-14-regular */
- font-family: var(--font-family-sans);
- font-size: 14px;
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 142.857% */
- letter-spacing: var(--letter-spacing-normal);
-
- &:focus {
- outline: none;
- }
-
- &::placeholder {
- color: var(--text-weak);
- }
- }
-
- textarea[data-slot="input-input"] {
- height: auto;
- min-height: 32px;
- padding: 6px 12px;
- resize: none;
- }
-
- [data-slot="input-copy-button"] {
- flex-shrink: 0;
- margin-top: 4px;
- color: var(--icon-base);
-
- &:hover {
- color: var(--icon-strong-base);
- }
- }
-
- [data-slot="input-error"] {
- color: var(--text-on-critical-base);
-
- /* 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: 18px; /* 150% */
- letter-spacing: var(--letter-spacing-normal);
- }
- }
+ width: 100%;
+
+ [data-slot="input-input"] {
+ width: 100%;
+ color: var(--text-strong);
+
+ /* text-14-regular */
+ font-family: var(--font-family-sans);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 142.857% */
+ letter-spacing: var(--letter-spacing-normal);
+
+ &:focus {
+ outline: none;
+ }
+
+ &::placeholder {
+ color: var(--text-weak);
+ }
+ }
+
+ &[data-variant="normal"] {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+
+ [data-slot="input-label"] {
+ color: var(--text-weak);
+
+ /* 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: 18px; /* 150% */
+ letter-spacing: var(--letter-spacing-normal);
+ }
+
+ [data-slot="input-wrapper"] {
+ display: flex;
+ align-items: start;
+ justify-content: space-between;
+ width: 100%;
+ padding-right: 4px;
+
+ border-radius: var(--radius-md);
+ border: 1px solid var(--border-weak-base);
+ background: var(--input-base);
+
+ &:focus-within:not(:has([data-readonly])) {
+ border-color: transparent;
+ /* border/shadow-xs/select */
+ box-shadow:
+ 0 0 0 3px var(--border-weak-selected),
+ 0 0 0 1px var(--border-selected),
+ 0 1px 2px -1px rgba(19, 16, 16, 0.25),
+ 0 1px 2px 0 rgba(19, 16, 16, 0.08),
+ 0 1px 3px 0 rgba(19, 16, 16, 0.12);
+ }
+
+ &:has([data-invalid]) {
+ background: var(--surface-critical-weak);
+ border: 1px solid var(--border-critical-selected);
+ }
+
+ &:not(:has([data-slot="input-copy-button"])) {
+ padding-right: 0;
+ }
+ }
+
+ [data-slot="input-input"] {
+ color: var(--text-strong);
+
+ display: flex;
+ height: 32px;
+ padding: 2px 12px;
+ align-items: center;
+ flex: 1;
+ min-width: 0;
+
+ background: transparent;
+ border: none;
+
+ /* text-14-regular */
+ font-family: var(--font-family-sans);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 142.857% */
+ letter-spacing: var(--letter-spacing-normal);
+
+ &:focus {
+ outline: none;
+ }
+
+ &::placeholder {
+ color: var(--text-weak);
+ }
+ }
+
+ textarea[data-slot="input-input"] {
+ height: auto;
+ min-height: 32px;
+ padding: 6px 12px;
+ resize: none;
+ }
+
+ [data-slot="input-copy-button"] {
+ flex-shrink: 0;
+ margin-top: 4px;
+ color: var(--icon-base);
+
+ &:hover {
+ color: var(--icon-strong-base);
+ }
+ }
+
+ [data-slot="input-error"] {
+ color: var(--text-on-critical-base);
+
+ /* 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: 18px; /* 150% */
+ letter-spacing: var(--letter-spacing-normal);
+ }
+ }
}
diff --git a/packages/ui/src/components/toast.css b/packages/ui/src/components/toast.css
index 1459bb189..d02896e78 100644
--- a/packages/ui/src/components/toast.css
+++ b/packages/ui/src/components/toast.css
@@ -1,205 +1,205 @@
[data-component="toast-region"] {
- position: fixed;
- bottom: 48px;
- right: 32px;
- z-index: 1000;
- display: flex;
- flex-direction: column;
- gap: 8px;
- max-width: 400px;
- width: 100%;
- pointer-events: none;
-
- [data-slot="toast-list"] {
- display: flex;
- flex-direction: column;
- gap: 8px;
- list-style: none;
- margin: 0;
- padding: 0;
- }
+ position: fixed;
+ bottom: 48px;
+ right: 32px;
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ max-width: 400px;
+ width: 100%;
+ pointer-events: none;
+
+ [data-slot="toast-list"] {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ }
}
[data-component="toast"] {
- display: flex;
- align-items: flex-start;
- gap: 20px;
- padding: 16px 20px;
- pointer-events: auto;
- transition: all 150ms ease-out;
-
- border-radius: var(--radius-lg);
- border: 1px solid var(--border-weak-base);
- background: var(--surface-float-base);
- color: var(--text-invert-base);
- box-shadow: var(--shadow-md);
-
- [data-slot="toast-inner"] {
- display: flex;
- align-items: flex-start;
- gap: 10px;
- }
-
- &[data-opened] {
- animation: toastPopIn 150ms ease-out;
- }
-
- &[data-closed] {
- animation: toastPopOut 100ms ease-in forwards;
- }
-
- &[data-swipe="move"] {
- transform: translateX(var(--kb-toast-swipe-move-x));
- }
-
- &[data-swipe="cancel"] {
- transform: translateX(0);
- transition: transform 200ms ease-out;
- }
-
- &[data-swipe="end"] {
- animation: toastSwipeOut 100ms ease-out forwards;
- }
-
- /* &[data-variant="success"] { */
- /* border-color: var(--color-semantic-positive); */
- /* } */
- /**/
- /* &[data-variant="error"] { */
- /* border-color: var(--color-semantic-danger); */
- /* } */
- /**/
- /* &[data-variant="loading"] { */
- /* border-color: var(--color-semantic-info); */
- /* } */
-
- [data-slot="toast-icon"] {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
-
- [data-component="icon"] {
- color: var(--text-invert-stronger);
- /* color: var(--icon-invert-base); */
- }
- }
-
- [data-slot="toast-content"] {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 2px;
- min-width: 0;
- }
-
- [data-slot="toast-title"] {
- color: var(--text-invert-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);
-
- margin: 0;
- }
-
- [data-slot="toast-description"] {
- color: var(--text-invert-base);
- text-wrap-style: pretty;
-
- /* text-14-regular */
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-x-large); /* 171.429% */
- letter-spacing: var(--letter-spacing-normal);
-
- margin: 0;
- }
-
- [data-slot="toast-actions"] {
- display: flex;
- gap: 16px;
- margin-top: 8px;
- }
-
- [data-slot="toast-action"] {
- background: none;
- border: none;
- padding: 0;
- cursor: pointer;
-
- color: var(--text-invert-weak);
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large);
- letter-spacing: var(--letter-spacing-normal);
-
- &:hover {
- text-decoration: underline;
- }
-
- &:first-child {
- color: var(--text-invert-strong);
- }
- }
-
- [data-slot="toast-close-button"] {
- flex-shrink: 0;
- }
-
- [data-slot="toast-progress-track"] {
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: 3px;
- background-color: var(--surface-base);
- border-radius: 0 0 var(--radius-lg) var(--radius-lg);
- overflow: hidden;
- }
-
- [data-slot="toast-progress-fill"] {
- height: 100%;
- width: var(--kb-toast-progress-fill-width);
- background-color: var(--color-primary);
- transition: width 250ms linear;
- }
+ display: flex;
+ align-items: flex-start;
+ gap: 20px;
+ padding: 16px 20px;
+ pointer-events: auto;
+ transition: all 150ms ease-out;
+
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--border-weak-base);
+ background: var(--surface-float-base);
+ color: var(--text-invert-base);
+ box-shadow: var(--shadow-md);
+
+ [data-slot="toast-inner"] {
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ &[data-opened] {
+ animation: toastPopIn 150ms ease-out;
+ }
+
+ &[data-closed] {
+ animation: toastPopOut 100ms ease-in forwards;
+ }
+
+ &[data-swipe="move"] {
+ transform: translateX(var(--kb-toast-swipe-move-x));
+ }
+
+ &[data-swipe="cancel"] {
+ transform: translateX(0);
+ transition: transform 200ms ease-out;
+ }
+
+ &[data-swipe="end"] {
+ animation: toastSwipeOut 100ms ease-out forwards;
+ }
+
+ /* &[data-variant="success"] { */
+ /* border-color: var(--color-semantic-positive); */
+ /* } */
+ /**/
+ /* &[data-variant="error"] { */
+ /* border-color: var(--color-semantic-danger); */
+ /* } */
+ /**/
+ /* &[data-variant="loading"] { */
+ /* border-color: var(--color-semantic-info); */
+ /* } */
+
+ [data-slot="toast-icon"] {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ [data-component="icon"] {
+ color: var(--text-invert-stronger);
+ /* color: var(--icon-invert-base); */
+ }
+ }
+
+ [data-slot="toast-content"] {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ min-width: 0;
+ }
+
+ [data-slot="toast-title"] {
+ color: var(--text-invert-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);
+
+ margin: 0;
+ }
+
+ [data-slot="toast-description"] {
+ color: var(--text-invert-base);
+ text-wrap-style: pretty;
+
+ /* text-14-regular */
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-x-large); /* 171.429% */
+ letter-spacing: var(--letter-spacing-normal);
+
+ margin: 0;
+ }
+
+ [data-slot="toast-actions"] {
+ display: flex;
+ gap: 16px;
+ margin-top: 8px;
+ }
+
+ [data-slot="toast-action"] {
+ background: none;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+
+ color: var(--text-invert-weak);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+
+ &:hover {
+ text-decoration: underline;
+ }
+
+ &:first-child {
+ color: var(--text-invert-strong);
+ }
+ }
+
+ [data-slot="toast-close-button"] {
+ flex-shrink: 0;
+ }
+
+ [data-slot="toast-progress-track"] {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 3px;
+ background-color: var(--surface-base);
+ border-radius: 0 0 var(--radius-lg) var(--radius-lg);
+ overflow: hidden;
+ }
+
+ [data-slot="toast-progress-fill"] {
+ height: 100%;
+ width: var(--kb-toast-progress-fill-width);
+ background-color: var(--color-primary);
+ transition: width 250ms linear;
+ }
}
@keyframes toastPopIn {
- from {
- opacity: 0;
- transform: translateY(20px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
@keyframes toastPopOut {
- from {
- opacity: 1;
- transform: translateY(0);
- }
- to {
- opacity: 0;
- transform: translateY(20px);
- }
+ from {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ to {
+ opacity: 0;
+ transform: translateY(20px);
+ }
}
@keyframes toastSwipeOut {
- from {
- transform: translateX(var(--kb-toast-swipe-end-x));
- }
- to {
- transform: translateX(100%);
- }
+ from {
+ transform: translateX(var(--kb-toast-swipe-end-x));
+ }
+ to {
+ transform: translateX(100%);
+ }
}
diff --git a/packages/ui/src/components/tooltip.css b/packages/ui/src/components/tooltip.css
index f02c2ca63..4abeb81aa 100644
--- a/packages/ui/src/components/tooltip.css
+++ b/packages/ui/src/components/tooltip.css
@@ -1,74 +1,74 @@
[data-component="tooltip-trigger"] {
- display: flex;
+ display: flex;
}
[data-slot="tooltip-keybind"] {
- display: flex;
- align-items: center;
- gap: 12px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
}
[data-slot="tooltip-keybind-key"] {
- color: var(--text-invert-base);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large);
+ color: var(--text-invert-base);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
}
[data-component="tooltip"] {
- z-index: 1000;
- max-width: 320px;
- border-radius: var(--radius-sm);
- background-color: var(--surface-float-base);
- color: var(--text-invert-strong);
- background: var(--surface-float-base);
- padding: 2px 8px;
- border: 1px solid var(--border-weak-base, rgba(0, 0, 0, 0.07));
+ z-index: 1000;
+ max-width: 320px;
+ border-radius: var(--radius-sm);
+ background-color: var(--surface-float-base);
+ color: var(--text-invert-strong);
+ background: var(--surface-float-base);
+ padding: 2px 8px;
+ border: 1px solid var(--border-weak-base, rgba(0, 0, 0, 0.07));
- box-shadow: var(--shadow-md);
- pointer-events: none !important;
- /* transition: all 150ms ease-out; */
- /* transform: translate3d(0, 0, 0); */
- /* transform-origin: var(--kb-tooltip-content-transform-origin); */
+ box-shadow: var(--shadow-md);
+ pointer-events: none !important;
+ /* transition: all 150ms ease-out; */
+ /* transform: translate3d(0, 0, 0); */
+ /* transform-origin: var(--kb-tooltip-content-transform-origin); */
- /* 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);
+ /* 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-expanded] {
- opacity: 1;
- /* transform: translate3d(0, 0, 0); */
- }
+ &[data-expanded] {
+ opacity: 1;
+ /* transform: translate3d(0, 0, 0); */
+ }
- &[data-closed]:not([data-force-open="true"]) {
- opacity: 0;
- }
+ &[data-closed]:not([data-force-open="true"]) {
+ opacity: 0;
+ }
- /* &[data-placement="top"] { */
- /* &[data-closed] { */
- /* transform: translate3d(0, 4px, 0); */
- /* } */
- /* } */
- /**/
- /* &[data-placement="bottom"] { */
- /* &[data-closed] { */
- /* transform: translate3d(0, -4px, 0); */
- /* } */
- /* } */
- /**/
- /* &[data-placement="left"] { */
- /* &[data-closed] { */
- /* transform: translate3d(4px, 0, 0); */
- /* } */
- /* } */
- /**/
- /* &[data-placement="right"] { */
- /* &[data-closed] { */
- /* transform: translate3d(-4px, 0, 0); */
- /* } */
- /* } */
+ /* &[data-placement="top"] { */
+ /* &[data-closed] { */
+ /* transform: translate3d(0, 4px, 0); */
+ /* } */
+ /* } */
+ /**/
+ /* &[data-placement="bottom"] { */
+ /* &[data-closed] { */
+ /* transform: translate3d(0, -4px, 0); */
+ /* } */
+ /* } */
+ /**/
+ /* &[data-placement="left"] { */
+ /* &[data-closed] { */
+ /* transform: translate3d(4px, 0, 0); */
+ /* } */
+ /* } */
+ /**/
+ /* &[data-placement="right"] { */
+ /* &[data-closed] { */
+ /* transform: translate3d(-4px, 0, 0); */
+ /* } */
+ /* } */
}
diff --git a/packages/ui/src/components/typewriter.css b/packages/ui/src/components/typewriter.css
index e978312a9..f99f2d8da 100644
--- a/packages/ui/src/components/typewriter.css
+++ b/packages/ui/src/components/typewriter.css
@@ -1,14 +1,14 @@
@keyframes blink {
- 0%,
- 50% {
- opacity: 1;
- }
- 51%,
- 100% {
- opacity: 0;
- }
+ 0%,
+ 50% {
+ opacity: 1;
+ }
+ 51%,
+ 100% {
+ opacity: 0;
+ }
}
.blinking-cursor {
- animation: blink 1s step-end infinite;
+ animation: blink 1s step-end infinite;
}
diff --git a/packages/ui/src/context/dialog.tsx b/packages/ui/src/context/dialog.tsx
index afba5f648..bd47b86d3 100644
--- a/packages/ui/src/context/dialog.tsx
+++ b/packages/ui/src/context/dialog.tsx
@@ -28,6 +28,7 @@ const Context = createContext<ReturnType<typeof init>>()
function init() {
const [active, setActive] = createSignal<Active | undefined>()
+ const [renders, setRenders] = createSignal<Record<string, JSX.Element>>({})
const timer = { current: undefined as ReturnType<typeof setTimeout> | undefined }
const lock = { value: false }
@@ -118,12 +119,28 @@ function init() {
setActive({ id, node, dispose, owner, onClose, setClosing })
}
+ const render = (element: JSX.Element, id: string, owner: Owner) => {
+ setRenders((renders) => ({ ...renders, [id]: element }))
+ show(() => element, owner, () => {
+ setRenders((renders) => {
+ const { [id]: _, ...rest } = renders
+ return rest
+ })
+ })
+ }
+
+ const isActive = (id: string) => {
+ return renders()[id] !== undefined
+ }
+
return {
get active() {
return active()
},
+ isActive,
close,
show,
+ render,
}
}
@@ -152,10 +169,17 @@ export function useDialog() {
get active() {
return ctx.active
},
+ isActive(id: string) {
+ return ctx.isActive(id)
+ },
show(element: DialogElement, onClose?: () => void) {
const base = ctx.active?.owner ?? owner
ctx.show(element, base, onClose)
},
+ render(element: JSX.Element, id: string) {
+ const base = ctx.active?.owner ?? owner
+ ctx.render(element, id, base)
+ },
close() {
ctx.close()
},
diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css
index 3ed0310ef..7c8548734 100644
--- a/packages/ui/src/styles/index.css
+++ b/packages/ui/src/styles/index.css
@@ -33,8 +33,10 @@
@import "../components/markdown.css" layer(components);
@import "../components/message-part.css" layer(components);
@import "../components/message-nav.css" layer(components);
+@import "../components/morph-chevron.css" layer(components);
@import "../components/popover.css" layer(components);
@import "../components/progress-circle.css" layer(components);
+@import "../components/reasoning-icon.css" layer(components);
@import "../components/radio-group.css" layer(components);
@import "../components/resize-handle.css" layer(components);
@import "../components/select.css" layer(components);
diff --git a/packages/ui/src/styles/utilities.css b/packages/ui/src/styles/utilities.css
index 8c954f1fe..990259acc 100644
--- a/packages/ui/src/styles/utilities.css
+++ b/packages/ui/src/styles/utilities.css
@@ -1,131 +1,174 @@
:root {
- interpolate-size: allow-keywords;
-
- [data-popper-positioner] {
- pointer-events: none;
- }
-
- /* ::selection { */
- /* background-color: color-mix(in srgb, var(--color-primary) 33%, transparent); */
- /* background-color: var(--color-primary); */
- /* color: var(--color-background); */
- /* } */
-
- ::-webkit-scrollbar-track {
- background: transparent;
- }
-
- ::-webkit-scrollbar-thumb {
- background-color: var(--surface-float-base);
- border-radius: var(--radius-md);
- }
-
- * {
- scrollbar-color: var(--surface-float-base) transparent;
- }
+ interpolate-size: allow-keywords;
+
+ /* Transition tokens */
+ --transition-duration: 200ms;
+ --transition-easing: cubic-bezier(0.25, 0, 0.5, 1);
+ --transition-fast: 150ms;
+ --transition-slow: 300ms;
+
+ /* Allow height transitions from 0 to auto */
+ @supports (interpolate-size: allow-keywords) {
+ interpolate-size: allow-keywords;
+ }
+
+ [data-popper-positioner] {
+ pointer-events: none;
+ }
+
+ /* ::selection { */
+ /* background-color: color-mix(in srgb, var(--color-primary) 33%, transparent); */
+ /* background-color: var(--color-primary); */
+ /* color: var(--color-background); */
+ /* } */
+
+ ::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ background-color: var(--surface-float-base);
+ border-radius: var(--radius-md);
+ }
+
+ * {
+ scrollbar-color: var(--surface-float-base) transparent;
+ }
}
.no-scrollbar {
- &::-webkit-scrollbar {
- display: none;
- }
- /* Hide scrollbar for IE, Edge and Firefox */
- & {
- -ms-overflow-style: none; /* IE and Edge */
- scrollbar-width: none; /* Firefox */
- }
+ &::-webkit-scrollbar {
+ display: none;
+ }
+ /* Hide scrollbar for IE, Edge and Firefox */
+ & {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+ }
}
.sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border-width: 0;
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
}
.truncate-start {
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- direction: rtl;
- text-align: left;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ direction: rtl;
+ text-align: left;
}
.text-12-regular {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 166.667% */
- letter-spacing: var(--letter-spacing-normal);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 166.667% */
+ letter-spacing: var(--letter-spacing-normal);
}
.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);
+ 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);
}
.text-12-mono {
- font-family: var(--font-family-mono);
- font-feature-settings: var(--font-feature-settings-mono);
- font-size: var(--font-size-small);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 166.667% */
- letter-spacing: var(--letter-spacing-normal);
+ font-family: var(--font-family-mono);
+ font-feature-settings: var(--font-feature-settings-mono);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 166.667% */
+ letter-spacing: var(--letter-spacing-normal);
}
.text-14-regular {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-x-large); /* 171.429% */
- letter-spacing: var(--letter-spacing-normal);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-x-large); /* 171.429% */
+ letter-spacing: var(--letter-spacing-normal);
}
.text-14-medium {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-large); /* 171.429% */
- letter-spacing: var(--letter-spacing-normal);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large); /* 171.429% */
+ letter-spacing: var(--letter-spacing-normal);
}
.text-14-mono {
- font-family: var(--font-family-mono);
- font-feature-settings: var(--font-feature-settings-mono);
- font-size: var(--font-size-base);
- font-style: normal;
- font-weight: var(--font-weight-regular);
- line-height: var(--line-height-large); /* 171.429% */
- letter-spacing: var(--letter-spacing-normal);
+ font-family: var(--font-family-mono);
+ font-feature-settings: var(--font-feature-settings-mono);
+ font-size: var(--font-size-base);
+ font-style: normal;
+ font-weight: var(--font-weight-regular);
+ line-height: var(--line-height-large); /* 171.429% */
+ letter-spacing: var(--letter-spacing-normal);
}
.text-16-medium {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-large);
- font-style: normal;
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-x-large); /* 150% */
- letter-spacing: var(--letter-spacing-tight);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-large);
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-x-large); /* 150% */
+ letter-spacing: var(--letter-spacing-tight);
}
.text-20-medium {
- font-family: var(--font-family-sans);
- font-size: var(--font-size-x-large);
- font-style: normal;
- font-weight: var(--font-weight-medium);
- line-height: var(--line-height-x-large); /* 120% */
- letter-spacing: var(--letter-spacing-tightest);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-x-large);
+ font-style: normal;
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-x-large); /* 120% */
+ letter-spacing: var(--letter-spacing-tightest);
+}
+
+/* Transition utility classes */
+.transition-colors {
+ transition-property: background-color, border-color, color, fill, stroke;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+}
+
+.transition-opacity {
+ transition-property: opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+}
+
+.transition-transform {
+ transition-property: transform;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+}
+
+.transition-shadow {
+ transition-property: box-shadow;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
+}
+
+.transition-interactive {
+ transition-property:
+ background-color, border-color, color, box-shadow, opacity;
+ transition-duration: var(--transition-duration);
+ transition-timing-function: var(--transition-easing);
}