diff options
| author | adamelmore <[email protected]> | 2026-01-25 06:20:44 -0600 |
|---|---|---|
| committer | adamelmore <[email protected]> | 2026-01-25 06:20:50 -0600 |
| commit | ddc4e893598b7aeb54d8476e97332ab97c02002f (patch) | |
| tree | f546f07d05dfbfab92e76975e2c44a312dd1eefa /packages/ui/src/components | |
| parent | a5c058e584dce24c47af5a88f6c83b69d79211d2 (diff) | |
| download | opencode-ddc4e893598b7aeb54d8476e97332ab97c02002f.tar.gz opencode-ddc4e893598b7aeb54d8476e97332ab97c02002f.zip | |
fix(app): cleanup comment component usage
Diffstat (limited to 'packages/ui/src/components')
| -rw-r--r-- | packages/ui/src/components/line-comment.css | 61 | ||||
| -rw-r--r-- | packages/ui/src/components/line-comment.tsx | 105 | ||||
| -rw-r--r-- | packages/ui/src/components/session-review.css | 62 | ||||
| -rw-r--r-- | packages/ui/src/components/session-review.tsx | 94 |
4 files changed, 185 insertions, 137 deletions
diff --git a/packages/ui/src/components/line-comment.css b/packages/ui/src/components/line-comment.css index 36fb14c64..9dc8eb74f 100644 --- a/packages/ui/src/components/line-comment.css +++ b/packages/ui/src/components/line-comment.css @@ -52,3 +52,64 @@ padding: 8px; border-radius: 14px; } + +[data-component="line-comment"] [data-slot="line-comment-content"] { + 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; +} + +[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; +} + +[data-component="line-comment"] [data-slot="line-comment-editor"] { + 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); +} + +[data-component="line-comment"] [data-slot="line-comment-textarea"]:focus { + 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; +} + +[data-component="line-comment"] [data-slot="line-comment-editor-label"] { + margin-right: auto; +} diff --git a/packages/ui/src/components/line-comment.tsx b/packages/ui/src/components/line-comment.tsx index 41462fa8e..f8869748c 100644 --- a/packages/ui/src/components/line-comment.tsx +++ b/packages/ui/src/components/line-comment.tsx @@ -1,4 +1,5 @@ -import { Show, type JSX } from "solid-js" +import { onMount, Show, splitProps, type JSX } from "solid-js" +import { Button } from "./button" import { Icon } from "./icon" export type LineCommentVariant = "default" | "editor" @@ -52,3 +53,105 @@ export const LineCommentAnchor = (props: LineCommentAnchorProps) => { </div> ) } + +export type LineCommentProps = Omit<LineCommentAnchorProps, "children" | "variant"> & { + comment: JSX.Element + selection: JSX.Element +} + +export const LineComment = (props: LineCommentProps) => { + const [split, rest] = splitProps(props, ["comment", "selection"]) + + return ( + <LineCommentAnchor {...rest} variant="default"> + <div data-slot="line-comment-content"> + <div data-slot="line-comment-text">{split.comment}</div> + <div data-slot="line-comment-label">Comment on {split.selection}</div> + </div> + </LineCommentAnchor> + ) +} + +export type LineCommentEditorProps = Omit<LineCommentAnchorProps, "children" | "open" | "variant" | "onClick"> & { + value: string + selection: JSX.Element + onInput: (value: string) => void + onCancel: VoidFunction + onSubmit: (value: string) => void + placeholder?: string + rows?: number + autofocus?: boolean + cancelLabel?: string + submitLabel?: string +} + +export const LineCommentEditor = (props: LineCommentEditorProps) => { + const [split, rest] = splitProps(props, [ + "value", + "selection", + "onInput", + "onCancel", + "onSubmit", + "placeholder", + "rows", + "autofocus", + "cancelLabel", + "submitLabel", + ]) + + const refs = { + textarea: undefined as HTMLTextAreaElement | undefined, + } + + const focus = () => refs.textarea?.focus() + + const submit = () => { + const value = split.value.trim() + if (!value) return + split.onSubmit(value) + } + + onMount(() => { + if (split.autofocus === false) return + requestAnimationFrame(focus) + }) + + return ( + <LineCommentAnchor {...rest} open={true} variant="editor" onClick={() => focus()}> + <div data-slot="line-comment-editor"> + <textarea + ref={(el) => { + refs.textarea = el + }} + data-slot="line-comment-textarea" + rows={split.rows ?? 3} + placeholder={split.placeholder ?? "Add comment"} + value={split.value} + onInput={(e) => split.onInput(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key === "Escape") { + e.preventDefault() + e.stopPropagation() + split.onCancel() + return + } + if (e.key !== "Enter") return + if (e.shiftKey) return + e.preventDefault() + e.stopPropagation() + submit() + }} + /> + <div data-slot="line-comment-actions"> + <div data-slot="line-comment-editor-label">Commenting on {split.selection}</div> + <Button size="small" variant="ghost" onClick={split.onCancel}> + {split.cancelLabel ?? "Cancel"} + </Button> + <Button size="small" variant="primary" disabled={split.value.trim().length === 0} onClick={submit}> + {split.submitLabel ?? "Comment"} + </Button> + </div> + </div> + </LineCommentAnchor> + ) +} diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css index 7c77c0e35..dd75b1905 100644 --- a/packages/ui/src/components/session-review.css +++ b/packages/ui/src/components/session-review.css @@ -75,68 +75,6 @@ overflow: hidden; } - [data-slot="session-review-comment-content"] { - display: flex; - flex-direction: column; - gap: 6px; - } - - [data-slot="session-review-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; - } - - [data-slot="session-review-comment-label"], - [data-slot="session-review-comment-draft-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; - } - - [data-slot="session-review-comment-draft"] { - display: flex; - flex-direction: column; - gap: 8px; - } - - [data-slot="session-review-comment-textarea"] { - width: 100%; - max-width: min(380px, calc(100vw - 48px)); - 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); - - &:focus { - outline: none; - box-shadow: var(--shadow-xs-border-select); - } - } - - [data-slot="session-review-comment-actions"] { - display: flex; - align-items: center; - gap: 8px; - } - - [data-slot="session-review-comment-draft-label"] { - margin-right: auto; - } - [data-slot="session-review-trigger-content"] { display: flex; align-items: center; diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 9bc7d82c1..1ae0b1a13 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -4,7 +4,7 @@ import { RadioGroup } from "./radio-group" import { DiffChanges } from "./diff-changes" import { FileIcon } from "./file-icon" import { Icon } from "./icon" -import { LineCommentAnchor } from "./line-comment" +import { LineComment, LineCommentEditor } from "./line-comment" import { StickyAccordionHeader } from "./sticky-accordion-header" import { useDiffComponent } from "../context/diff" import { useI18n } from "../context/i18n" @@ -305,7 +305,6 @@ export const SessionReview = (props: SessionReviewProps) => { <For each={props.diffs}> {(diff) => { let wrapper: HTMLDivElement | undefined - let textarea: HTMLTextAreaElement | undefined const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === diff.file)) const commentedLines = createMemo(() => comments().map((c) => c.selection)) @@ -396,7 +395,6 @@ export const SessionReview = (props: SessionReviewProps) => { if (!range) return setDraft("") scheduleAnchors() - requestAnimationFrame(() => textarea?.focus()) }) createEffect(() => { @@ -565,7 +563,7 @@ export const SessionReview = (props: SessionReviewProps) => { <For each={comments()}> {(comment) => ( - <LineCommentAnchor + <LineComment id={comment.id} top={positions()[comment.id]} onMouseEnter={() => setSelection({ file: comment.file, range: comment.selection })} @@ -578,83 +576,31 @@ export const SessionReview = (props: SessionReviewProps) => { openComment(comment) }} open={isCommentOpen(comment)} - > - <div data-slot="session-review-comment-content"> - <div data-slot="session-review-comment-text">{comment.comment}</div> - <div data-slot="session-review-comment-label"> - Comment on {selectionLabel(comment.selection)} - </div> - </div> - </LineCommentAnchor> + comment={comment.comment} + selection={selectionLabel(comment.selection)} + /> )} </For> <Show when={draftRange()}> {(range) => ( <Show when={draftTop() !== undefined}> - <LineCommentAnchor + <LineCommentEditor top={draftTop()} - onClick={() => textarea?.focus()} - open={true} - variant="editor" - > - <div data-slot="session-review-comment-draft"> - <textarea - ref={textarea} - data-slot="session-review-comment-textarea" - rows={3} - placeholder="Add comment" - value={draft()} - onInput={(e) => setDraft(e.currentTarget.value)} - onKeyDown={(e) => { - if (e.key === "Escape") { - e.preventDefault() - e.stopPropagation() - setCommenting(null) - return - } - if (e.key !== "Enter") return - if (e.shiftKey) return - e.preventDefault() - const value = draft().trim() - if (!value) return - props.onLineComment?.({ - file: diff.file, - selection: range(), - comment: value, - preview: selectionPreview(diff, range()), - }) - setCommenting(null) - }} - /> - <div data-slot="session-review-comment-actions"> - <div data-slot="session-review-comment-draft-label"> - Commenting on {selectionLabel(range())} - </div> - <Button size="small" variant="ghost" onClick={() => setCommenting(null)}> - Cancel - </Button> - <Button - size="small" - variant="primary" - disabled={draft().trim().length === 0} - onClick={() => { - const value = draft().trim() - if (!value) return - props.onLineComment?.({ - file: diff.file, - selection: range(), - comment: value, - preview: selectionPreview(diff, range()), - }) - setCommenting(null) - }} - > - Comment - </Button> - </div> - </div> - </LineCommentAnchor> + value={draft()} + selection={selectionLabel(range())} + onInput={setDraft} + onCancel={() => setCommenting(null)} + onSubmit={(comment) => { + props.onLineComment?.({ + file: diff.file, + selection: range(), + comment, + preview: selectionPreview(diff, range()), + }) + setCommenting(null) + }} + /> </Show> )} </Show> |
