diff options
| author | David Hill <[email protected]> | 2025-12-16 10:54:51 +0000 |
|---|---|---|
| committer | David Hill <[email protected]> | 2025-12-16 10:54:51 +0000 |
| commit | 05e0759878cb0f24c981c69ae26f6be3ea5583c6 (patch) | |
| tree | 39aaf6b86a6f2b8e653120e80d8f0facb528051b /packages/ui/src | |
| parent | 2330ec6dc3000ae8b86810e9d59b414ad4f05f47 (diff) | |
| parent | 75e5130cf8f58b32ee3f3ba2249d5917e7e3d6fc (diff) | |
| download | opencode-05e0759878cb0f24c981c69ae26f6be3ea5583c6.tar.gz opencode-05e0759878cb0f24c981c69ae26f6be3ea5583c6.zip | |
Merge branch 'dev' of https://github.com/sst/opencode into dev
Diffstat (limited to 'packages/ui/src')
23 files changed, 390 insertions, 127 deletions
diff --git a/packages/ui/src/assets/audio/nope-01.aac b/packages/ui/src/assets/audio/nope-01.aac Binary files differnew file mode 100644 index 000000000..9fb614d08 --- /dev/null +++ b/packages/ui/src/assets/audio/nope-01.aac diff --git a/packages/ui/src/assets/audio/nope-02.aac b/packages/ui/src/assets/audio/nope-02.aac Binary files differnew file mode 100644 index 000000000..75603cc16 --- /dev/null +++ b/packages/ui/src/assets/audio/nope-02.aac diff --git a/packages/ui/src/assets/audio/nope-03.aac b/packages/ui/src/assets/audio/nope-03.aac Binary files differnew file mode 100644 index 000000000..1fe459a16 --- /dev/null +++ b/packages/ui/src/assets/audio/nope-03.aac diff --git a/packages/ui/src/assets/audio/nope-04.aac b/packages/ui/src/assets/audio/nope-04.aac Binary files differnew file mode 100644 index 000000000..b731a2a07 --- /dev/null +++ b/packages/ui/src/assets/audio/nope-04.aac diff --git a/packages/ui/src/assets/audio/nope-05.aac b/packages/ui/src/assets/audio/nope-05.aac Binary files differnew file mode 100644 index 000000000..4534191b6 --- /dev/null +++ b/packages/ui/src/assets/audio/nope-05.aac diff --git a/packages/ui/src/components/code.tsx b/packages/ui/src/components/code.tsx index c80f0987f..77696faed 100644 --- a/packages/ui/src/components/code.tsx +++ b/packages/ui/src/components/code.tsx @@ -1,4 +1,4 @@ -import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/precision-diffs" +import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/diffs" import { ComponentProps, createEffect, createMemo, splitProps } from "solid-js" import { createDefaultOptions, styleVariables } from "../pierre" import { workerPool } from "../pierre/worker" diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx index 47d6af42e..40a6ac83d 100644 --- a/packages/ui/src/components/dialog.tsx +++ b/packages/ui/src/components/dialog.tsx @@ -20,6 +20,14 @@ export function Dialog(props: DialogProps) { ...(props.classList ?? {}), [props.class ?? ""]: !!props.class, }} + onOpenAutoFocus={(e) => { + const target = e.currentTarget as HTMLElement | null + const autofocusEl = target?.querySelector("[autofocus]") as HTMLElement | null + if (autofocusEl) { + e.preventDefault() + autofocusEl.focus() + } + }} > <Show when={props.title || props.action}> <div data-slot="dialog-header"> diff --git a/packages/ui/src/components/diff-ssr.tsx b/packages/ui/src/components/diff-ssr.tsx index 800aa3730..b38b4a34f 100644 --- a/packages/ui/src/components/diff-ssr.tsx +++ b/packages/ui/src/components/diff-ssr.tsx @@ -1,5 +1,5 @@ -import { FileDiff } from "@pierre/precision-diffs" -import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr" +import { FileDiff } from "@pierre/diffs" +import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" import { onCleanup, onMount, Show, splitProps } from "solid-js" import { isServer } from "solid-js/web" import { createDefaultOptions, styleVariables, type DiffProps } from "../pierre" @@ -65,11 +65,11 @@ export function Diff<T>(props: SSRDiffProps<T>) { return ( <div data-component="diff" style={styleVariables} ref={container}> - <file-diff ref={fileDiffRef} id="ssr-diff"> + <diffs-container ref={fileDiffRef} id="ssr-diff"> <Show when={isServer}> <template shadowrootmode="open" innerHTML={props.preloadedDiff.prerenderedHTML} /> </Show> - </file-diff> + </diffs-container> </div> ) } diff --git a/packages/ui/src/components/diff.css b/packages/ui/src/components/diff.css index 690667ea7..345271a12 100644 --- a/packages/ui/src/components/diff.css +++ b/packages/ui/src/components/diff.css @@ -19,8 +19,8 @@ position: sticky; background-color: var(--surface-diff-hidden-base); color: var(--text-base); - width: var(--pjs-column-content-width); - left: var(--pjs-column-number-width); + width: var(--diffs-column-content-width); + left: var(--diffs-column-number-width); padding-left: 8px; user-select: none; cursor: default; diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx index 703043f4c..75dde0440 100644 --- a/packages/ui/src/components/diff.tsx +++ b/packages/ui/src/components/diff.tsx @@ -1,4 +1,4 @@ -import { FileDiff } from "@pierre/precision-diffs" +import { FileDiff } from "@pierre/diffs" import { createEffect, createMemo, onCleanup, splitProps } from "solid-js" import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre" import { workerPool } from "../pierre/worker" diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 0dbd7a650..b8e8106e8 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -51,6 +51,7 @@ const icons = { "circle-check": `<path d="M12.4987 7.91732L8.7487 12.5007L7.08203 10.834M17.9154 10.0007C17.9154 14.3729 14.371 17.9173 9.9987 17.9173C5.62644 17.9173 2.08203 14.3729 2.08203 10.0007C2.08203 5.6284 5.62644 2.08398 9.9987 2.08398C14.371 2.08398 17.9154 5.6284 17.9154 10.0007Z" stroke="currentColor" stroke-linecap="square"/>`, copy: `<path d="M6.2513 6.24935V2.91602H17.0846V13.7493H13.7513M13.7513 6.24935V17.0827H2.91797V6.24935H13.7513Z" stroke="currentColor" stroke-linecap="round"/>`, check: `<path d="M5 11.9657L8.37838 14.7529L15 5.83398" stroke="currentColor" stroke-linecap="square"/>`, + photo: `<path d="M16.6665 16.6666L11.6665 11.6666L9.99984 13.3333L6.6665 9.99996L3.08317 13.5833M2.9165 2.91663H17.0832V17.0833H2.9165V2.91663ZM13.3332 7.49996C13.3332 8.30537 12.6803 8.95829 11.8748 8.95829C11.0694 8.95829 10.4165 8.30537 10.4165 7.49996C10.4165 6.69454 11.0694 6.04163 11.8748 6.04163C12.6803 6.04163 13.3332 6.69454 13.3332 7.49996Z" stroke="currentColor" stroke-linecap="square"/>`, } export interface IconProps extends ComponentProps<"svg"> { diff --git a/packages/ui/src/components/list.css b/packages/ui/src/components/list.css index cd9e73d1d..368065e53 100644 --- a/packages/ui/src/components/list.css +++ b/packages/ui/src/components/list.css @@ -112,6 +112,7 @@ padding: 4px 10px; align-items: center; color: var(--text-strong); + scroll-margin-top: 28px; /* text-14-medium */ font-family: var(--font-family-sans); diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx index 7ec6e159d..0ed745f32 100644 --- a/packages/ui/src/components/list.tsx +++ b/packages/ui/src/components/list.tsx @@ -79,7 +79,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void }) return } const element = scrollRef()?.querySelector(`[data-key="${active()}"]`) - element?.scrollIntoView({ block: "nearest", behavior: "smooth" }) + element?.scrollIntoView({ block: "center", behavior: "smooth" }) }) const handleSelect = (item: T | undefined, index: number) => { diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index e369f9220..01f34ceff 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -14,11 +14,78 @@ line-height: var(--line-height-large); letter-spacing: var(--letter-spacing-normal); color: var(--text-base); - display: -webkit-box; - line-clamp: 3; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; + 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-base); + border: 1px solid var(--border-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"] { + display: -webkit-box; + white-space: pre-wrap; + line-clamp: 3; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + } + + .text-text-strong { + color: var(--text-strong); + } + + .font-medium { + font-weight: var(--font-weight-medium); + } } [data-component="text-part"] { @@ -108,15 +175,19 @@ 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; font-family: var(--font-family-sans); font-size: var(--font-size-base); font-style: normal; @@ -129,14 +200,22 @@ [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"] { @@ -151,6 +230,23 @@ border-top: 1px solid var(--border-weaker-base); } +[data-component="write-content"] { + border-top: 1px solid var(--border-weaker-base); + max-height: 240px; + overflow-y: auto; + + [data-component="code"] { + padding-bottom: 0px !important; + } + + /* Hide scrollbar */ + scrollbar-width: none; + -ms-overflow-style: none; + &::-webkit-scrollbar { + display: none; + } +} + [data-component="tool-action"] { width: 24px; height: 24px; diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index f00c43bd8..33b519ea4 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -2,6 +2,7 @@ import { Component, createMemo, For, Match, Show, Switch } from "solid-js" import { Dynamic } from "solid-js/web" import { AssistantMessage, + FilePart, Message as MessageType, Part as PartType, TextPart, @@ -74,13 +75,93 @@ export function AssistantMessageDisplay(props: { message: AssistantMessage; part } export function UserMessageDisplay(props: { message: UserMessage; parts: PartType[] }) { - const text = createMemo(() => - props.parts - ?.filter((p) => p.type === "text" && !(p as TextPart).synthetic) - ?.map((p) => (p as TextPart).text) - ?.join(""), + const textPart = createMemo( + () => props.parts?.find((p) => p.type === "text" && !(p as TextPart).synthetic) as TextPart | undefined, + ) + + const text = createMemo(() => textPart()?.text || "") + + const files = createMemo(() => (props.parts?.filter((p) => p.type === "file") as FilePart[]) ?? []) + + const attachments = createMemo(() => + files()?.filter((f) => { + const mime = f.mime + return mime.startsWith("image/") || mime === "application/pdf" + }), + ) + + const inlineFiles = createMemo(() => + files().filter((f) => { + const mime = f.mime + return !mime.startsWith("image/") && mime !== "application/pdf" && f.source?.text?.start !== undefined + }), + ) + + return ( + <div data-component="user-message"> + <Show when={attachments().length > 0}> + <div data-slot="user-message-attachments"> + <For each={attachments()}> + {(file) => ( + <div data-slot="user-message-attachment" data-type={file.mime.startsWith("image/") ? "image" : "file"}> + <Show + when={file.mime.startsWith("image/") && file.url} + fallback={ + <div data-slot="user-message-attachment-icon"> + <Icon name="folder" /> + </div> + } + > + <img data-slot="user-message-attachment-image" src={file.url} alt={file.filename ?? "attachment"} /> + </Show> + </div> + )} + </For> + </div> + </Show> + <Show when={text()}> + <div data-slot="user-message-text"> + <HighlightedText text={text()} references={inlineFiles()} /> + </div> + </Show> + </div> + ) +} + +function HighlightedText(props: { text: string; references: FilePart[] }) { + const segments = createMemo(() => { + const text = props.text + const refs = [...props.references].sort((a, b) => (a.source?.text?.start ?? 0) - (b.source?.text?.start ?? 0)) + + const result: { text: string; highlight?: boolean }[] = [] + let lastIndex = 0 + + for (const ref of refs) { + const start = ref.source?.text?.start + const end = ref.source?.text?.end + + if (start === undefined || end === undefined || start < lastIndex) continue + + if (start > lastIndex) { + result.push({ text: text.slice(lastIndex, start) }) + } + + result.push({ text: text.slice(start, end), highlight: true }) + lastIndex = end + } + + if (lastIndex < text.length) { + result.push({ text: text.slice(lastIndex) }) + } + + return result + }) + + return ( + <For each={segments()}> + {(segment) => <span classList={{ "text-text-strong font-medium": segment.highlight }}>{segment.text}</span>} + </For> ) - return <div data-component="user-message">{text()}</div> } export function Part(props: MessagePartProps) { @@ -220,8 +301,12 @@ ToolRegistry.register({ render(props) { return ( <BasicTool icon="bullet-list" trigger={{ title: "List", subtitle: getDirectory(props.input.path || "/") }}> - <Show when={false && props.output}> - <div data-component="tool-output">{props.output}</div> + <Show when={props.output}> + {(output) => ( + <div data-component="tool-output" data-scrollable> + <Markdown text={output()} /> + </div> + )} </Show> </BasicTool> ) @@ -240,8 +325,12 @@ ToolRegistry.register({ args: props.input.pattern ? ["pattern=" + props.input.pattern] : [], }} > - <Show when={false && props.output}> - <div data-component="tool-output">{props.output}</div> + <Show when={props.output}> + {(output) => ( + <div data-component="tool-output" data-scrollable> + <Markdown text={output()} /> + </div> + )} </Show> </BasicTool> ) @@ -263,8 +352,12 @@ ToolRegistry.register({ args, }} > - <Show when={false && props.output}> - <div data-component="tool-output">{props.output}</div> + <Show when={props.output}> + {(output) => ( + <div data-component="tool-output" data-scrollable> + <Markdown text={output()} /> + </div> + )} </Show> </BasicTool> ) @@ -288,8 +381,12 @@ ToolRegistry.register({ ), }} > - <Show when={false && props.output}> - <div data-component="tool-output">{props.output}</div> + <Show when={props.output}> + {(output) => ( + <div data-component="tool-output" data-scrollable> + <Markdown text={output()} /> + </div> + )} </Show> </BasicTool> ) @@ -308,8 +405,12 @@ ToolRegistry.register({ subtitle: props.input.description, }} > - <Show when={false && props.output}> - <div data-component="tool-output">{props.output}</div> + <Show when={props.output}> + {(output) => ( + <div data-component="tool-output" data-scrollable> + <Markdown text={output()} /> + </div> + )} </Show> </BasicTool> ) @@ -387,6 +488,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "write", render(props) { + console.log(props) return ( <BasicTool icon="code-lines" @@ -405,9 +507,19 @@ ToolRegistry.register({ </div> } > - <Show when={false && props.output}> - <div data-component="tool-output">{props.output}</div> - </Show> + {/* <Show when={props.input.content}> */} + {/* <div data-component="write-content"> */} + {/* <Code */} + {/* file={{ */} + {/* name: props.input.filePath, */} + {/* contents: props.input.content, */} + {/* cacheKey: checksum(props.input.content), */} + {/* }} */} + {/* overflow="scroll" */} + {/* class="pb-40" */} + {/* /> */} + {/* </div> */} + {/* </Show> */} </BasicTool> ) }, @@ -418,6 +530,7 @@ ToolRegistry.register({ render(props) { return ( <BasicTool + defaultOpen icon="checklist" trigger={{ title: "To-dos", diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 8009091b7..b47ab55b1 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -9,7 +9,7 @@ import { getDirectory, getFilename } from "@opencode-ai/util/path" import { For, Match, Show, Switch, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { type FileDiff } from "@opencode-ai/sdk/v2" -import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr" +import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" import { Dynamic } from "solid-js/web" import { checksum } from "@opencode-ai/util/encode" diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index ad2e6c36e..0f5a26a2a 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -24,6 +24,8 @@ export function SessionTurn( props: ParentProps<{ sessionID: string messageID: string + stepsExpanded?: boolean + onStepsExpandedChange?: (expanded: boolean) => void classes?: { root?: string content?: string @@ -60,11 +62,12 @@ export function SessionTurn( function handleScroll() { if (!scrollRef) return - // prevents scroll loops - if (working() && scrollRef.scrollTop < 100) return - setState("scrollY", scrollRef.scrollTop) if (state.autoScrolling) return const { scrollTop, scrollHeight, clientHeight } = scrollRef + const scrollRoom = scrollHeight - clientHeight + if (scrollRoom > 100) { + setState("scrollY", scrollTop) + } const atBottom = scrollHeight - scrollTop - clientHeight < 50 if (!atBottom && working()) { setState("userScrolled", true) @@ -222,11 +225,17 @@ export function SessionTurn( const [store, setStore] = createStore({ status: rawStatus(), - stepsExpanded: true, + stepsExpanded: props.stepsExpanded ?? working(), duration: duration(), }) createEffect(() => { + if (props.stepsExpanded !== undefined) { + setStore("stepsExpanded", props.stepsExpanded) + } + }) + + createEffect(() => { const timer = setInterval(() => { setStore("duration", duration()) }, 1000) @@ -260,8 +269,13 @@ export function SessionTurn( createEffect((prev) => { const isWorking = working() + if (!prev && isWorking) { + setStore("stepsExpanded", true) + props.onStepsExpandedChange?.(true) + } if (prev && !isWorking && !state.userScrolled) { setStore("stepsExpanded", false) + props.onStepsExpandedChange?.(false) } return isWorking }, working()) @@ -278,7 +292,7 @@ export function SessionTurn( <div data-slot="session-turn-message-header"> <div data-slot="session-turn-message-title"> <Switch> - <Match when={working()}> + <Match when={working() && message().id === userMessages().at(-1)?.id}> <Typewriter as="h1" text={message().summary?.title} data-slot="session-turn-typewriter" /> </Match> <Match when={true}> @@ -298,7 +312,11 @@ export function SessionTurn( data-slot="session-turn-collapsible-trigger-content" variant="ghost" size="small" - onClick={() => setStore("stepsExpanded", !store.stepsExpanded)} + onClick={() => { + const next = !store.stepsExpanded + setStore("stepsExpanded", next) + props.onStepsExpandedChange?.(next) + }} > <Show when={working()}> <Spinner /> diff --git a/packages/ui/src/context/data.tsx b/packages/ui/src/context/data.tsx index 265178e10..16efe7779 100644 --- a/packages/ui/src/context/data.tsx +++ b/packages/ui/src/context/data.tsx @@ -1,6 +1,6 @@ import type { Message, Session, Part, FileDiff, SessionStatus } from "@opencode-ai/sdk/v2" import { createSimpleContext } from "./helper" -import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr" +import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" type Data = { session: Session[] diff --git a/packages/ui/src/context/dialog.tsx b/packages/ui/src/context/dialog.tsx index af5da06f9..71fc63806 100644 --- a/packages/ui/src/context/dialog.tsx +++ b/packages/ui/src/context/dialog.tsx @@ -1,79 +1,105 @@ -import { For, Show, type JSX } from "solid-js" -import { createStore } from "solid-js/store" -import { createSimpleContext } from "@opencode-ai/ui/context" +import { + createContext, + createEffect, + createSignal, + getOwner, + Owner, + ParentProps, + runWithOwner, + Show, + useContext, + type JSX, +} from "solid-js" +import { Dialog as Kobalte } from "@kobalte/core/dialog" + +type DialogElement = () => JSX.Element -type DialogElement = JSX.Element | (() => JSX.Element) +const Context = createContext<ReturnType<typeof init>>() -export const { use: useDialog, provider: DialogProvider } = createSimpleContext({ - name: "Dialog", - init: () => { - const [store, setStore] = createStore({ - stack: [] as { +function init() { + const [active, setActive] = createSignal< + | { + id: string element: DialogElement onClose?: () => void - }[], - }) + owner: Owner + } + | undefined + >() - return { - get stack() { - return store.stack - }, - push(element: DialogElement, onClose?: () => void) { - setStore("stack", (s) => [...s, { element, onClose }]) - }, - pop() { - const current = store.stack.at(-1) - current?.onClose?.() - setStore("stack", store.stack.slice(0, -1)) - }, - replace(element: DialogElement, onClose?: () => void) { - for (const item of store.stack) { - item.onClose?.() - } - setStore("stack", [{ element, onClose }]) - }, - clear() { - for (const item of store.stack) { - item.onClose?.() - } - setStore("stack", []) - }, - } - }, -}) + const result = { + get active() { + return active() + }, + close() { + active()?.onClose?.() + setActive(undefined) + }, + show(element: DialogElement, owner: Owner, onClose?: () => void) { + active()?.onClose?.() + const id = Math.random().toString(36).slice(2) + setActive({ + id, + element: () => + runWithOwner(owner, () => ( + <Show when={active()?.id === id}> + <Kobalte + modal + open={true} + onOpenChange={(open) => { + if (!open) { + console.log("closing") + result.close() + } + }} + > + <Kobalte.Portal> + <Kobalte.Overlay data-component="dialog-overlay" /> + {element()} + </Kobalte.Portal> + </Kobalte> + </Show> + )), + onClose, + owner, + }) + }, + } -import { Dialog as Kobalte } from "@kobalte/core/dialog" + return result +} -export function DialogRoot(props: { children?: JSX.Element }) { - const dialog = useDialog() +export function DialogProvider(props: ParentProps) { + const ctx = init() + createEffect(() => { + console.log("active", ctx.active) + }) return ( - <> + <Context.Provider value={ctx}> {props.children} - <Show when={dialog.stack.length > 0}> - <div data-component="dialog-stack"> - <For each={dialog.stack}> - {(item, index) => ( - <Show when={index() === dialog.stack.length - 1}> - <Kobalte - modal - defaultOpen - onOpenChange={(open) => { - if (!open) { - item.onClose?.() - dialog.pop() - } - }} - > - <Kobalte.Portal> - <Kobalte.Overlay data-component="dialog-overlay" /> - {typeof item.element === "function" ? item.element() : item.element} - </Kobalte.Portal> - </Kobalte> - </Show> - )} - </For> - </div> - </Show> - </> + <div data-component="dialog-stack">{ctx.active?.element?.()}</div> + </Context.Provider> ) } + +export function useDialog() { + const ctx = useContext(Context) + const owner = getOwner() + if (!owner) { + throw new Error("useDialog must be used within a DialogProvider") + } + if (!ctx) { + throw new Error("useDialog must be used within a DialogProvider") + } + return { + get active() { + return ctx.active + }, + show(element: DialogElement, onClose?: () => void) { + ctx.show(element, owner, onClose) + }, + close() { + ctx.close() + }, + } +} diff --git a/packages/ui/src/context/marked.tsx b/packages/ui/src/context/marked.tsx index 0d9c44758..f4d85519d 100644 --- a/packages/ui/src/context/marked.tsx +++ b/packages/ui/src/context/marked.tsx @@ -2,7 +2,7 @@ import { marked } from "marked" import markedShiki from "marked-shiki" import { bundledLanguages, type BundledLanguage } from "shiki" import { createSimpleContext } from "./helper" -import { getSharedHighlighter, registerCustomTheme, ThemeRegistrationResolved } from "@pierre/precision-diffs" +import { getSharedHighlighter, registerCustomTheme, ThemeRegistrationResolved } from "@pierre/diffs" registerCustomTheme("OpenCode", () => { return Promise.resolve({ diff --git a/packages/ui/src/custom-elements.d.ts b/packages/ui/src/custom-elements.d.ts index 6ad3ea34e..b756e51da 100644 --- a/packages/ui/src/custom-elements.d.ts +++ b/packages/ui/src/custom-elements.d.ts @@ -1,12 +1,12 @@ /** - * TypeScript declaration for the <file-diff> custom element. - * This tells TypeScript that <file-diff> is a valid JSX element in SolidJS. - * Required for using the precision-diffs web component in .tsx files. + * TypeScript declaration for the <diffs-container> custom element. + * This tells TypeScript that <diffs-container> is a valid JSX element in SolidJS. + * Required for using the @pierre/diffs web component in .tsx files. */ declare module "solid-js" { namespace JSX { interface IntrinsicElements { - "file-diff": HTMLAttributes<HTMLElement> + "diffs-container": HTMLAttributes<HTMLElement> } } } diff --git a/packages/ui/src/pierre/index.ts b/packages/ui/src/pierre/index.ts index 8780bc6c5..f83fc82a2 100644 --- a/packages/ui/src/pierre/index.ts +++ b/packages/ui/src/pierre/index.ts @@ -1,4 +1,4 @@ -import { DiffLineAnnotation, FileContents, FileDiffOptions } from "@pierre/precision-diffs" +import { DiffLineAnnotation, FileContents, FileDiffOptions } from "@pierre/diffs" import { ComponentProps } from "solid-js" export type DiffProps<T = {}> = FileDiffOptions<T> & { @@ -10,8 +10,8 @@ export type DiffProps<T = {}> = FileDiffOptions<T> & { } const unsafeCSS = ` -[data-pjs-header], -[data-pjs] { +[data-diffs-header], +[data-diffs] { [data-separator-wrapper] { margin: 0 !important; border-radius: 0 !important; @@ -71,12 +71,12 @@ export function createDefaultOptions<T>(style: FileDiffOptions<T>["diffStyle"]) } export const styleVariables = { - "--pjs-font-family": "var(--font-family-mono)", - "--pjs-font-size": "var(--font-size-small)", - "--pjs-line-height": "24px", - "--pjs-tab-size": 2, - "--pjs-font-features": "var(--font-family-mono--font-feature-settings)", - "--pjs-header-font-family": "var(--font-family-sans)", - "--pjs-gap-block": 0, - "--pjs-min-number-column-width": "4ch", + "--diffs-font-family": "var(--font-family-mono)", + "--diffs-font-size": "var(--font-size-small)", + "--diffs-line-height": "24px", + "--diffs-tab-size": 2, + "--diffs-font-features": "var(--font-family-mono--font-feature-settings)", + "--diffs-header-font-family": "var(--font-family-sans)", + "--diffs-gap-block": 0, + "--diffs-min-number-column-width": "4ch", } diff --git a/packages/ui/src/pierre/worker.ts b/packages/ui/src/pierre/worker.ts index 2b2da1f09..e47268d4e 100644 --- a/packages/ui/src/pierre/worker.ts +++ b/packages/ui/src/pierre/worker.ts @@ -1,5 +1,5 @@ -import { getOrCreateWorkerPoolSingleton } from "@pierre/precision-diffs/worker" -import ShikiWorkerUrl from "@pierre/precision-diffs/worker/worker.js?worker&url" +import { getOrCreateWorkerPoolSingleton } from "@pierre/diffs/worker" +import ShikiWorkerUrl from "@pierre/diffs/worker/worker.js?worker&url" export function workerFactory(): Worker { return new Worker(ShikiWorkerUrl, { type: "module" }) |
